PluginProbe
Friends / 4.3.2
Friends v4.3.2
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
← All changes | includes/class-messages.php +541 -187 2.8.14.3.2 View file →
@@ -18,8 +18,9 @@
18 18 * @author Alex Kirk
19 19 */
20 20 class Messages {
21 21 const CPT = 'friend_message';
22 + const TAXONOMY = 'friend_message_user';
22 23
23 24 /**
24 25 * Contains a reference to the Friends class.
25 26 *
@@ -41,19 +42,32 @@
41 42 * Register the WordPress hooks
42 43 */
43 44 private function register_hooks() {
44 45 add_action( 'init', array( $this, 'register_custom_post_type' ) );
46 + add_action( 'init', array( $this, 'register_taxonomy' ) );
47 + add_filter( 'post_type_link', array( $this, 'post_type_link' ), 10, 2 );
45 48 add_filter( 'friends_unread_count', array( $this, 'friends_unread_messages_count' ) );
46 49 add_action( 'friends_own_site_menu_top', array( $this, 'friends_add_menu_unread_messages' ) );
47 50 add_action( 'wp_ajax_friends-mark-read', array( $this, 'mark_message_read' ) );
48 - add_action( 'rest_api_init', array( $this, 'add_rest_routes' ) );
51 + add_action( 'wp_ajax_friends-get-message-delivery-statuses', array( $this, 'get_message_delivery_statuses' ) );
49 52 add_action( 'friends_author_header', array( $this, 'friends_author_header' ), 10, 2 );
50 53 add_action( 'friends_after_header', array( $this, 'friends_display_messages' ), 10, 2 );
51 54 add_action( 'friends_after_header', array( $this, 'friends_message_form' ), 11, 2 );
52 55 add_filter( 'template_redirect', array( $this, 'handle_message_send' ), 10, 2 );
56 + add_filter( 'friends_send_direct_message', array( $this, 'save_outgoing_message' ), 10, 6 );
57 + add_filter( 'notify_friend_message_received', array( $this, 'save_incoming_message' ), 5, 6 );
58 + add_filter( 'mastodon_api_conversation', array( $this, 'mastodon_api_conversation' ), 10, 2 );
59 + add_filter( 'mastodon_api_conversations', array( $this, 'mastodon_api_conversations' ), 10, 3 );
60 + add_filter( 'mastodon_api_status_context_post_types', array( $this, 'api_status_context_post_types' ), 10, 2 );
61 + add_filter( 'mastodon_api_status_context_post_statuses', array( $this, 'api_status_context_post_statuses' ), 10, 2 );
62 + add_filter( 'api_status_context_post_types', array( $this, 'api_status_context_post_types' ), 10, 2 ); // legacy filter.
63 + add_filter( 'api_status_context_post_statuses', array( $this, 'api_status_context_post_statuses' ), 10, 2 ); // legacy filter.
64 + add_filter( 'mastodon_api_submit_status', array( $this, 'mastodon_api_submit_status' ), 9, 6 );
65 + add_filter( 'mastodon_api_conversation_mark_read', array( $this, 'mastodon_api_conversation_mark_read' ), 10 );
66 + add_filter( 'mastodon_api_conversation_delete', array( $this, 'delete_conversation' ), 10 );
67 + add_filter( 'mastodon_api_status', array( $this, 'mastodon_api_status' ), 20, 2 );
53 68 }
54 69
55 -
56 70 /**
57 71 * Registers the custom post type
58 72 */
59 73 public function register_custom_post_type() {
@@ -121,122 +135,131 @@
121 135 )
122 136 );
123 137 }
124 138
125 - /**
126 - * Add the REST API to send and receive friend requests
127 - */
128 - public function add_rest_routes() {
129 - register_rest_route(
130 - REST::PREFIX,
131 - 'message',
132 - array(
133 - 'methods' => 'POST',
134 - 'callback' => array( $this, 'rest_receive_message' ),
135 - 'permission_callback' => Friends::authenticated_for_posts(),
136 - )
139 + public function register_taxonomy() {
140 + $args = array(
141 + 'labels' => array(
142 + 'name' => _x( 'Friend Message User', 'taxonomy general name', 'friends' ),
143 + 'singular_name' => _x( 'Friend Message User', 'taxonomy singular name', 'friends' ),
144 + 'menu_name' => __( 'Friend Message User', 'friends' ),
145 + ),
146 + 'hierarchical' => false,
147 + 'show_ui' => true,
148 + 'show_admin_column' => true,
149 + 'query_var' => true,
150 + 'rewrite' => false,
151 + 'public' => false,
137 152 );
153 + register_taxonomy( self::TAXONOMY, self::CPT, $args );
138 154 }
139 155
156 +
157 +
158 +
159 + public function friends_frontend_post_types_only_messages( $post_types ) {
160 + $post_types = array( self::CPT );
161 + return $post_types;
162 + }
163 +
140 164 /**
141 - * Receive a message via REST
165 + * Look up a direct message by its URL.
142 166 *
143 - * @param \WP_REST_Request $request The incoming request.
144 - * @return array The array to be returned via the REST API.
167 + * Messages live in their own post type which is not part of the frontend post
168 + * types, so Feed::url_to_postid() doesn't consider them by default.
169 + *
170 + * @param string $url The URL of the message.
171 + *
172 + * @return int|null The post ID or null if it wasn't found.
145 173 */
146 - public function rest_receive_message( \WP_REST_Request $request ) {
147 - $tokens = explode( '-', $request->get_param( 'auth' ) );
148 - $user_id = $this->friends->access_control->verify_token( $tokens[0], isset( $tokens[1] ) ? $tokens[1] : null, isset( $tokens[2] ) ? $tokens[2] : null );
149 - if ( ! $user_id ) {
150 - return new \WP_Error(
151 - 'friends_request_failed',
152 - __( 'Could not respond to the request.', 'friends' ),
153 - array(
154 - 'status' => 403,
155 - )
156 - );
174 + public static function url_to_postid( $url ) {
175 + $only_messages = function () {
176 + return array( self::CPT );
177 + };
178 +
179 + add_filter( 'friends_frontend_post_types', $only_messages, 999 );
180 + $post_id = Feed::url_to_postid( $url );
181 + remove_filter( 'friends_frontend_post_types', $only_messages, 999 );
182 +
183 + return $post_id;
184 + }
185 +
186 + public function save_incoming_message( User $friend_user, $message, $subject = '', $feed_url = null, $remote_url = null, $reply_to = null ) {
187 + $post_data = array(
188 + 'post_type' => self::CPT,
189 + 'post_title' => $subject,
190 + 'post_content' => $message,
191 + 'post_status' => 'friends_unread',
192 + 'guid' => $remote_url,
193 + );
194 +
195 + if ( $reply_to ) {
196 + add_filter( 'friends_frontend_post_types', array( $this, 'friends_frontend_post_types_only_messages' ), 999 );
197 + $reply_to_post_id = Feed::url_to_postid( $reply_to );
198 + remove_filter( 'friends_frontend_post_types', array( $this, 'friends_frontend_post_types_only_messages' ), 999 );
199 + if ( $reply_to_post_id ) {
200 + $topmost_post = get_post( $reply_to_post_id );
201 + while ( $topmost_post->post_parent ) {
202 + $topmost_post = get_post( $topmost_post->post_parent );
203 + }
204 +
205 + $post_data['post_parent'] = $topmost_post->ID;
206 + }
157 207 }
158 208
159 - $friend_user = new User( $user_id );
160 - if ( ! $friend_user->has_cap( self::get_minimum_cap() ) ) {
161 - return new \WP_Error(
162 - 'friends_request_failed',
163 - __( 'Could not respond to the request.', 'friends' ),
164 - array(
165 - 'status' => 403,
166 - )
167 - );
209 + $post_id = $friend_user->insert_post( $post_data );
210 + wp_set_post_terms( $post_id, strval( $friend_user->ID ), self::TAXONOMY );
211 + if ( $feed_url ) {
212 + update_post_meta( $post_id, 'friends_feed_url', $feed_url );
168 213 }
169 214
170 - $subject = wp_unslash( $request->get_param( 'subject' ) );
171 - $message = wp_unslash( $request->get_param( 'message' ) );
172 - $this->add_message_to_post( $friend_user, $friend_user, $subject, $message, true );
173 -
174 - do_action( 'notify_friend_message_received', $friend_user, $message, $subject );
175 -
176 - return array(
177 - 'status' => 'message-received',
178 - );
215 + return $post_id;
179 216 }
180 217
181 218 /**
182 219 * Adds a message to friends_message post.
183 220 *
184 - * @param \WP_User $sender The sender.
185 - * @param User $friend_user The friend user to which this should be associated.
186 - * @param string $subject The subject.
187 - * @param string $message The message.
221 + * @param int $post_id The return value that might be a post id.
222 + * @param User $friend_user The friend user to which this should be associated.
223 + * @param string $to The recipient as URL.
224 + * @param string $message The message.
225 + * @param string $subject The subject.
226 + * @param int $reply_to_post_id The reply to post ID.
188 227 *
189 228 * @return int The post ID.
190 229 */
191 - private function add_message_to_post( \WP_User $sender, User $friend_user, $subject, $message ) {
192 - $existing_message = new \WP_Query(
193 - array(
194 - 'post_type' => self::CPT,
195 - 'author' => $friend_user->ID,
196 - 'title' => $subject,
197 - 'post_status' => array( 'friends_read', 'friends_unread' ),
198 - )
230 + public function save_outgoing_message( $post_id, User $friend_user, $to, $message, $subject = '', $reply_to_post_id = null ) {
231 + $content = \wpautop( $message );
232 + $content = \preg_replace( '/[\n\r\t]/', '', $content );
233 + $content = \trim( $content );
234 +
235 + $post_data = array(
236 + 'post_type' => self::CPT,
237 + 'post_title' => $subject,
238 + 'post_content' => $content,
239 + 'post_status' => 'friends_read',
240 + 'post_parent' => $reply_to_post_id,
199 241 );
200 - $mark_unread = $sender->ID === $friend_user->ID;
201 242
202 - $content = '';
203 - $content .= '<!-- wp:friends/message {"sender":' . $sender->ID . ',"date":' . time() . '} -->' . PHP_EOL;
204 - $content .= '<div class="wp-block-friends-message">';
205 - $content .= '<span class="date">' . esc_html( gmdate( 'Y-m-d H:i:s' ) ) . '</span> ';
206 - $content .= '<strong>' . esc_html( $sender->display_name ) . '</strong>: ';
207 - if ( false === strpos( $message, '<!-- wp:' ) ) {
208 - $content .= '<!-- wp:paragraph -->' . PHP_EOL . '<p>' . wp_kses_post( $message );
209 - $content .= '</p>' . PHP_EOL;
210 - $content .= '<!-- /wp:paragraph -->';
211 - } else {
212 - $content .= wp_kses_post( $message );
243 + $post_id = wp_insert_post( $post_data );
244 + wp_set_post_terms( $post_id, strval( $friend_user->ID ), self::TAXONOMY );
245 + if ( $to ) {
246 + update_post_meta( $post_id, 'friends_feed_url', $to );
213 247 }
214 - $content .= '</div>';
215 - $content .= '<!-- /wp:friends/message -->';
216 248
217 - if ( $existing_message->have_posts() ) {
218 - $post = $existing_message->next_post();
219 - $post_id = $post->ID;
220 - wp_update_post(
221 - array(
222 - 'ID' => $post->ID,
223 - 'post_content' => $post->post_content . PHP_EOL . $content,
224 - 'post_status' => $mark_unread ? 'friends_unread' : 'friends_read',
225 - )
226 - );
227 - } else {
228 - $post_id = $friend_user->insert_post(
229 - array(
230 - 'post_type' => self::CPT,
231 - 'post_title' => $subject,
232 - 'post_content' => $content,
233 - 'post_status' => $mark_unread ? 'friends_unread' : 'friends_read',
234 - )
235 - );
249 + return $post_id;
250 + }
251 +
252 + public function post_type_link( $post_link, \WP_Post $post ) {
253 + if ( $post && self::CPT === $post->post_type ) {
254 + if ( str_starts_with( $post->guid, home_url() ) || empty( $post->guid ) ) {
255 + return home_url( '?p=' . $post->ID );
256 + }
257 +
258 + return $post->guid;
236 259 }
237 260
238 - return $post_id;
261 + return $post_link;
239 262 }
240 263
241 264 /**
242 265 * Add the unread messages to the total.
@@ -268,11 +291,30 @@
268 291 'post_status' => 'friends_unread',
269 292 )
270 293 );
271 294
295 + $title = esc_html__( 'Direct Messages', 'friends' );
296 + if ( $unread_messages->post_count > 0 ) {
297 + $title .= ' <span class="wp-core-ui wp-ui-notification friends-open-requests">' . esc_html( number_format_i18n( $unread_messages->post_count ) ) . '</span>';
298 + }
299 +
300 + $wp_menu->add_menu(
301 + array(
302 + 'id' => 'friends-direct-messages',
303 + 'parent' => 'friends-menu',
304 + 'title' => $title,
305 + 'href' => home_url( '/friends/messages/' ),
306 + )
307 + );
308 +
272 309 while ( $unread_messages->have_posts() ) {
273 310 $unread_messages->the_post();
274 - $friend_user = User::get_post_author( $post );
311 + $friend_user = User::get_post_author( $post );
312 + $conversation = $post;
313 + while ( $conversation->post_parent ) {
314 + $conversation = get_post( $conversation->post_parent );
315 + }
316 + $conversation_url = add_query_arg( 'conversation', $conversation->ID, home_url( '/friends/messages/' ) );
275 317 $wp_menu->add_menu(
276 318 array(
277 319 'id' => 'friend-message-' . $friend_user->ID,
278 320 'parent' => 'friends-menu',
@@ -277,9 +319,9 @@
277 319 'id' => 'friend-message-' . $friend_user->ID,
278 320 'parent' => 'friends-menu',
279 321 // translators: %s is the number of open friend requests.
280 322 'title' => '<span style="border-left: 2px solid #d63638; padding-left: .5em">' . esc_html( sprintf( __( 'New message from %s', 'friends' ), $friend_user->display_name ) ) . '</span>',
281 - 'href' => $friend_user->get_local_friends_page_url(),
323 + 'href' => $conversation_url,
282 324 )
283 325 );
284 326 }
285 327 }
@@ -309,36 +351,94 @@
309 351 'result' => false,
310 352 )
311 353 );
312 354 }
313 -
355 + $result = array();
314 356 $post_id = intval( $_POST['post_id'] );
315 - wp_update_post(
357 + if ( get_post_status( $post_id ) !== 'friends_read' ) {
358 + wp_update_post(
359 + array(
360 + 'ID' => $post_id,
361 + 'post_status' => 'friends_read',
362 + )
363 + );
364 + $result[] = $post_id;
365 + }
366 + $child_posts = get_children(
316 367 array(
317 - 'ID' => $post_id,
318 - 'post_status' => 'friends_read',
368 + 'post_parent' => $post_id,
369 + 'post_type' => self::CPT,
370 + 'post_status' => array( 'friends_unread' ),
319 371 )
320 372 );
373 + foreach ( $child_posts as $child ) {
374 + wp_update_post(
375 + array(
376 + 'ID' => $child->ID,
377 + 'post_status' => 'friends_read',
378 + )
379 + );
380 + $result[] = $child->ID;
381 + }
321 382
322 383 do_action( 'friends_message_read', $post_id );
323 384
324 385 wp_send_json_success(
325 386 array(
326 - 'result' => true,
387 + 'result' => $result,
327 388 )
328 389 );
329 390 }
330 391
331 392 /**
332 - * Adds the friend requests to the unread count.
333 - *
334 - * @param int $unread The unread count.
335 - *
336 - * @return int Unread count + friend requests.
393 + * Ajax function to fetch delivery status for outgoing direct messages.
337 394 */
338 - public function friends_unread_friend_request_count( $unread ) {
339 - $friend_requests = User_Query::all_friend_requests();
340 - return $unread + $friend_requests->get_total();
395 + public function get_message_delivery_statuses() {
396 + check_ajax_referer( 'friends-message-delivery-statuses' );
397 +
398 + if ( ! is_user_logged_in() ) {
399 + wp_send_json_error(
400 + array(
401 + 'message' => __( 'You are not authorized to view message delivery statuses.', 'friends' ),
402 + ),
403 + 403
404 + );
405 + }
406 +
407 + $post_ids = array();
408 + if ( isset( $_POST['post_ids'] ) && is_array( $_POST['post_ids'] ) ) {
409 + $post_ids = array_map( 'absint', wp_unslash( $_POST['post_ids'] ) );
410 + }
411 +
412 + $post_ids = array_filter( array_unique( $post_ids ) );
413 + if ( empty( $post_ids ) ) {
414 + wp_send_json_success( array( 'statuses' => array() ) );
415 + }
416 +
417 + $statuses = array();
418 + foreach ( $post_ids as $post_id ) {
419 + $post = get_post( $post_id );
420 + if ( ! $post || self::CPT !== $post->post_type || get_current_user_id() !== intval( $post->post_author ) ) {
421 + continue;
422 + }
423 +
424 + $delivery = null;
425 + if ( class_exists( 'Friends\Feed_Parser_ActivityPub' ) ) {
426 + $delivery = Feed_Parser_ActivityPub::get_direct_message_delivery_status( $post );
427 + }
428 +
429 + if ( ! $delivery ) {
430 + continue;
431 + }
432 +
433 + $statuses[ $post_id ] = array(
434 + 'status' => sanitize_html_class( $delivery['status'] ),
435 + 'label' => $delivery['label'],
436 + 'title' => $delivery['title'],
437 + );
438 + }
439 +
440 + wp_send_json_success( array( 'statuses' => $statuses ) );
341 441 }
342 442
343 443 /**
344 444 * Extend the author header.
@@ -346,9 +446,9 @@
346 446 * @param User $friend_user The friend user.
347 447 * @param array $args The arguments.
348 448 */
349 449 public function friends_author_header( User $friend_user, $args ) {
350 - if ( $friend_user->has_cap( self::get_minimum_cap() ) ) {
450 + if ( apply_filters( 'friends_message_form_accounts', array(), $friend_user ) ) {
351 451 Friends::template_loader()->get_template_part(
352 452 'frontend/messages/author-header',
353 453 null,
354 454 $args
@@ -366,21 +466,29 @@
366 466 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
367 467 return;
368 468 }
369 469
370 - if ( $args['friend_user']->has_cap( self::get_minimum_cap() ) ) {
371 - $args['existing_messages'] = new \WP_Query(
470 + if ( apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] ) ) {
471 + $args['existing_messages'] = new \WP_Query();
472 + $args['existing_messages']->set( 'post_type', self::CPT );
473 + $args['existing_messages']->set( 'post_parent', '0' );
474 + $args['existing_messages']->set( 'post_status', array( 'friends_read', 'friends_unread' ) );
475 + $tax_query = array();
476 + $tax_query[] =
372 477 array(
373 - 'post_type' => self::CPT,
374 - 'author' => $args['friend_user']->ID,
375 - 'post_status' => array( 'friends_read', 'friends_unread' ),
376 - )
377 - );
378 -
379 - if ( ! $args['existing_messages']->have_posts() ) {
478 + 'taxonomy' => self::TAXONOMY,
479 + 'field' => 'slug',
480 + 'terms' => $args['friend_user']->ID,
481 + );
482 + $args['existing_messages']->set( 'tax_query', $tax_query );
483 + $args['existing_messages']->set( 'posts_per_page', -1 );
484 + if ( ! $args['existing_messages']->get_posts() ) {
380 485 return;
381 486 }
487 + $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
382 488
489 + add_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
490 +
383 491 Friends::template_loader()->get_template_part(
384 492 'frontend/messages/friend',
385 493 null,
386 494 $args
@@ -385,11 +493,17 @@
385 493 null,
386 494 $args
387 495 );
388 496
497 + remove_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
498 +
389 499 }
390 500 }
391 501
502 + public function friends_message_excerpt_length() {
503 + return 10;
504 + }
505 +
392 506 /**
393 507 * Embed the message form for the friend user.
394 508 *
395 509 * @param array $args The arguments.
@@ -398,11 +512,12 @@
398 512 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
399 513 return;
400 514 }
401 515
402 - $args['blocks-everywhere'] = get_user_option( 'friends_blocks_everywhere' );
516 + $args['blocks-everywhere'] = false;
517 + $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
403 518
404 - if ( $args['friend_user']->has_cap( self::get_minimum_cap() ) ) {
519 + if ( $args['accounts'] ) {
405 520 Friends::template_loader()->get_template_part(
406 521 'frontend/messages/message-form',
407 522 null,
408 523 $args
@@ -411,64 +526,29 @@
411 526 }
412 527 }
413 528
414 529 /**
415 - * Gets the minimum capability necessary to use messages.
416 - *
417 - * @return string The minimum capability.
418 - */
419 - public static function get_minimum_cap() {
420 - return apply_filters( 'friends_message_minimum_cap', 'friend' );
421 - }
422 -
423 - /**
424 530 * Sends a message to a friend.
425 531 *
426 532 * @param User $friend_user The friend user.
533 + * @param string $to The recipient as URL.
427 534 * @param string $message The message.
428 535 * @param string $subject The subject.
536 + * @param int $reply_to_post_id The reply to post ID.
429 537 *
430 538 * @return \WP_Error|int An error or the message post id.
431 539 */
432 - public function send_message( User $friend_user, $message, $subject = null ) {
433 - if ( ! $friend_user->has_cap( self::get_minimum_cap() ) ) {
540 + public function send_message( User $friend_user, $to, $message, $subject = '', $reply_to_post_id = null ) {
541 + $tos = apply_filters( 'friends_message_form_accounts', array(), $friend_user );
542 + if ( ! isset( $tos[ $to ] ) ) {
434 543 return new \WP_Error( 'not-a-friend', __( 'You cannot send messages to this user.', 'friends' ) );
435 544 }
436 - if ( ! trim( $message ) ) {
545 + if ( ! $message || ! trim( $message ) ) {
437 546 return new \WP_Error( 'empty-message', __( 'You cannot send empty messages.', 'friends' ) );
438 547 }
439 548
440 - if ( empty( $subject ) ) {
441 - $subject = sprintf(
442 - // translators: %1$s is a date, %2$s is a time.
443 - __( 'Conversation started on %1$s at %2$s', 'friends' ),
444 - gmdate( 'Y-m-d' ),
445 - gmdate( 'H:i:s' )
446 - );
447 - }
549 + $post_id = apply_filters( 'friends_send_direct_message', null, $friend_user, $to, $message, $subject, $reply_to_post_id );
448 550
449 - $post_id = $this->add_message_to_post( wp_get_current_user(), $friend_user, $subject, $message );
450 -
451 - $body = array(
452 - 'subject' => $subject,
453 - 'message' => $message,
454 - 'auth' => $friend_user->get_friend_auth(),
455 - );
456 -
457 - $response = wp_safe_remote_post(
458 - $friend_user->get_rest_url() . '/message',
459 - array(
460 - 'body' => $body,
461 - 'timeout' => 20,
462 - 'redirection' => 5,
463 - )
464 - );
465 -
466 - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
467 - // TODO find a way to message the user.
468 - return new \WP_Error( 'invalid-response', __( 'We received an unexpected response to our message.', 'friends' ) );
469 - }
470 -
471 551 return $post_id;
472 552 }
473 553
474 554 /**
@@ -473,33 +553,37 @@
473 553
474 554 /**
475 555 * Delete a conversation
476 556 *
477 - * @param User $friend_user The friend user.
478 - * @param string $subject The subject.
557 + * @param int $parent_post_id The parent post id.
479 558 *
480 559 * @return \WP_Error|\WP_Post|bool The post or false.
481 560 */
482 - public function delete_conversation( User $friend_user, $subject ) {
483 - if ( ! $friend_user->has_cap( self::get_minimum_cap() ) ) {
484 - return new \WP_Error( 'not-a-friend', __( 'You cannot delete converations.', 'friends' ) );
561 + public function delete_conversation( $parent_post_id ) {
562 + if ( ! current_user_can( 'delete_post', $parent_post_id ) ) {
563 + return new \WP_Error( 'cannot_delete', __( 'You cannot delete converations.', 'friends' ) );
485 564 }
486 565
487 - $existing_message = new \WP_Query(
566 + $post = get_post( $parent_post_id );
567 + if ( ! $post ) {
568 + return new \WP_Error( 'friends_messages_delete_conversation', 'Record not found', array( 'status' => 404 ) );
569 + }
570 +
571 + $child_posts = get_children(
488 572 array(
573 + 'post_parent' => $parent_post_id,
489 574 'post_type' => self::CPT,
490 - 'author' => $friend_user->ID,
491 - 'title' => $subject,
492 575 'post_status' => array( 'friends_read', 'friends_unread' ),
493 576 )
494 577 );
495 578
496 - if ( $existing_message->have_posts() ) {
497 - $post = $existing_message->next_post();
498 - return wp_trash_post( $post->ID );
579 + foreach ( $child_posts as $child_post ) {
580 + wp_delete_post( $child_post->ID );
499 581 }
500 582
501 - return false;
583 + wp_delete_post( $parent_post_id );
584 +
585 + return true;
502 586 }
503 587
504 588 /**
505 589 * Handle the message form.
@@ -512,24 +596,53 @@
512 596 if ( isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
513 597 return $this->handle_conversation_delete();
514 598 }
515 599
516 - if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'friends_send_message' ) ) {
517 - wp_die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) );
600 + if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
601 + wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
518 602 }
519 603
520 - $friend_user = new User( $_REQUEST['friends_message_recipient'] );
604 + $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
521 605
522 - $subject = wp_unslash( $_REQUEST['friends_message_subject'] );
523 - $message = wp_unslash( $_REQUEST['friends_message_message'] );
606 + $subject = '';
607 + if ( isset( $_REQUEST['friends_message_subject'] ) ) {
608 + $subject = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_subject'] ) );
609 + }
610 + $message = '';
611 + if ( isset( $_REQUEST['friends_message_message'] ) ) {
612 + $message = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_message'] ) );
613 + }
614 + if ( ! trim( $message ) ) {
615 + wp_die( esc_html__( 'You cannot send an empty message.', 'friends' ) );
616 + }
524 617
525 - $error = $this->send_message( $friend_user, $message, $subject );
618 + $reply_to_post_id = null;
619 + if ( isset( $_REQUEST['friends_message_reply_to'] ) ) {
620 + $reply_to_post_id = intval( $_REQUEST['friends_message_reply_to'] );
621 + }
526 622
623 + $to = null;
624 + if ( isset( $_REQUEST['friends_message_account'] ) ) {
625 + $to = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_account'] ) );
626 + }
627 +
628 + if ( ! $to ) {
629 + wp_die( esc_html__( 'You cannot send a message without a recipient.', 'friends' ) );
630 + }
631 +
632 + $error = $this->send_message( $friend_user, $to, $message, $subject, $reply_to_post_id );
633 +
527 634 if ( is_wp_error( $error ) ) {
528 635 wp_die( esc_html( $error->get_error_message() ) );
529 636 }
530 637
531 - wp_safe_redirect( $friend_user->get_local_friends_page_url() );
638 + if ( isset( $_REQUEST['friends_message_redirect_to'] ) ) {
639 + $redirect_to = wp_validate_redirect( sanitize_url( wp_unslash( $_REQUEST['friends_message_redirect_to'] ) ), $friend_user->get_local_friends_page_url() );
640 + } else {
641 + $redirect_to = $friend_user->get_local_friends_page_url();
642 + }
643 +
644 + wp_safe_redirect( $redirect_to );
532 645 exit;
533 646 }
534 647
535 648 /**
@@ -535,25 +648,266 @@
535 648 /**
536 649 * Handle the deletion of the conversation.
537 650 */
538 651 public function handle_conversation_delete() {
539 - if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
652 + if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) || ! isset( $_REQUEST['friends_message_recipient'] ) ) {
540 653 return;
541 654 }
542 655
543 - if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'friends_send_message' ) ) {
544 - wp_die( esc_html( __( 'Error - unable to verify nonce, please try again.', 'friends' ) ) );
656 + if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
657 + wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
545 658 }
546 659
547 - $friend_user = new User( $_REQUEST['friends_message_recipient'] );
660 + $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
548 661
549 - $subject = wp_unslash( $_REQUEST['friends_message_subject'] );
550 - $error = $this->delete_conversation( $friend_user, $subject );
662 + $post_id = '';
663 + if ( isset( $_REQUEST['friends_message_reply_to'] ) ) {
664 + $post_id = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_reply_to'] ) );
665 + }
666 + if ( ! $post_id ) {
667 + wp_die( esc_html__( 'You cannot delete a conversation without a post id.', 'friends' ) );
668 + }
669 + $error = $this->delete_conversation( $post_id );
551 670
552 671 if ( is_wp_error( $error ) ) {
553 672 wp_die( esc_html( $error->get_error_message() ) );
554 673 }
555 674
556 - wp_safe_redirect( $friend_user->get_local_friends_page_url() );
675 + if ( isset( $_REQUEST['friends_message_redirect_to'] ) ) {
676 + $redirect_to = wp_validate_redirect( sanitize_url( wp_unslash( $_REQUEST['friends_message_redirect_to'] ) ), $friend_user->get_local_friends_page_url() );
677 + } else {
678 + $redirect_to = $friend_user->get_local_friends_page_url();
679 + }
680 +
681 + wp_safe_redirect( $redirect_to );
557 682 exit;
683 + }
684 +
685 + public function mastodon_api_conversation( $conversation, $post_id ) {
686 + $message = get_post( $post_id );
687 + if ( ! $message ) {
688 + return new \WP_Error( 'friends_messages_mastodon_api_conversation', 'Record not found', array( 'status' => 404 ) );
689 + }
690 +
691 + $last_status = get_posts(
692 + array(
693 + 'post_type' => self::CPT,
694 + 'post_parent' => $message->ID,
695 + 'post_status' => array( 'friends_read', 'friends_unread' ),
696 + 'orderby' => 'date',
697 + 'order' => 'DESC',
698 + 'numberposts' => 1,
699 + )
700 + );
701 + if ( ! $last_status ) {
702 + $last_status = $message;
703 + } else {
704 + $last_status = $last_status[0];
705 + }
706 +
707 + $unread = 'friends_unread' === $message->post_status;
708 + if ( ! $unread ) {
709 + $unread_posts = get_children(
710 + array(
711 + 'post_parent' => $message->ID,
712 + 'post_type' => self::CPT,
713 + 'post_status' => array( 'friends_unread' ),
714 + )
715 + );
716 + if ( $unread_posts ) {
717 + $unread = true;
718 + }
719 + }
720 + $conversation = new \Enable_Mastodon_Apps\Entity\Conversation();
721 + $conversation->id = $message->ID;
722 + $conversation->unread = $unread;
723 + $conversation->last_status = apply_filters( 'mastodon_api_status', null, $last_status->ID );
724 + $conversation->accounts = array();
725 + $account_id = $message->post_author;
726 + if ( ! $account_id ) {
727 + $account_id = get_post_meta( $message->ID, 'friends_feed_url', true );
728 + }
729 + $account = apply_filters( 'mastodon_api_account', null, $account_id );
730 + if ( $account ) {
731 + $conversation->accounts[] = $account;
732 + }
733 +
734 + return $conversation;
735 + }
736 +
737 + public function mastodon_api_conversations( $conversations, $user_id, $limit = 20 ) {
738 + $messages = new \WP_Query();
739 + $messages->set( 'post_type', self::CPT );
740 + $messages->set( 'post_parent', '0' );
741 + $messages->set( 'post_status', array( 'friends_read', 'friends_unread' ) );
742 + $messages->set( 'posts_per_page', $limit );
743 + $messages->set( 'order', 'DESC' );
744 +
745 + foreach ( $messages->get_posts() as $message ) {
746 + $conversation = $this->mastodon_api_conversation( null, $message->ID );
747 + if ( $conversation && ! is_wp_error( $conversation ) ) {
748 + $conversations[] = $conversation;
749 + }
750 + }
751 +
752 + return $conversations;
753 + }
754 +
755 + public function api_status_context_post_types( $post_types, $post_id ) {
756 + if ( self::CPT === get_post_type( $post_id ) ) {
757 + return array( self::CPT );
758 + }
759 + return $post_types;
760 + }
761 +
762 + public function api_status_context_post_statuses( $post_statuses, $post_id ) {
763 + if ( self::CPT === get_post_type( $post_id ) ) {
764 + return array( 'friends_read', 'friends_unread' );
765 + }
766 + return $post_statuses;
767 + }
768 +
769 + public function mastodon_api_submit_status( $status, $status_text, $in_reply_to_id, $media_ids, $post_format, $visibility ) {
770 + if ( $status instanceof \WP_Error || $status instanceof \Enable_Mastodon_Apps\Entity\Status || 'direct' !== $visibility ) {
771 + return $status;
772 + }
773 +
774 + $mentions = apply_filters(
775 + 'activitypub_extract_mentions',
776 + array(),
777 + $status_text,
778 + null
779 + );
780 +
781 + /**
782 + * Documented in Enable_Mastodon_Apps\Handler\Status\prepare_post_data()
783 + */
784 + $status_text = apply_filters( 'mastodon_api_submit_status_text', $status_text, $in_reply_to_id, $visibility );
785 +
786 + $friend_user = false;
787 + foreach ( $mentions as $mention ) {
788 + $user_feed = User_Feed::get_by_url( $mention );
789 + if ( $user_feed ) {
790 + $friend_user = $user_feed->get_friend_user();
791 + break;
792 + }
793 + if ( class_exists( Feed_Parser_ActivityPub::class ) ) {
794 + $user_feed = User_Feed::get_by_url( $mention );
795 + $url = Feed_Parser_ActivityPub::friends_webfinger_resolve( $mention, $mention );
796 + $user_feed = User_Feed::get_by_url( $url );
797 + if ( $user_feed ) {
798 + $friend_user = $user_feed->get_friend_user();
799 + break;
800 + }
801 + }
802 + }
803 +
804 + if ( ! $friend_user ) {
805 + // we cannot find the mentioned user, we need to stop this.
806 + // TODO: handle the non-ActivityPub case.
807 + return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Direct message without a known mentioned user', array( 'status' => 400 ) );
808 +
809 + }
810 +
811 + $app = \Enable_Mastodon_Apps\Mastodon_App::get_current_app();
812 + if ( ! $app->get_disable_blocks() ) {
813 + $status_text = \Enable_Mastodon_Apps\Handler\Status::convert_to_blocks( $status_text );
814 + }
815 +
816 + if ( ! empty( $media_ids ) ) {
817 + foreach ( $media_ids as $media_id ) {
818 + $media = get_post( $media_id );
819 + if ( ! $media ) {
820 + return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Media not found', array( 'status' => 400 ) );
821 + }
822 + if ( 'attachment' !== $media->post_type ) {
823 + return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Media not found', array( 'status' => 400 ) );
824 + }
825 + $attachment = \wp_get_attachment_metadata( $media_id );
826 + if ( \wp_attachment_is( 'image', $media_id ) ) {
827 + $status_text .= PHP_EOL;
828 + $meta_json = array(
829 + 'id' => intval( $media_id ),
830 + 'sizeSlug' => 'large',
831 + );
832 + $status_text .= '<!-- wp:image ' . wp_json_encode( $meta_json ) . ' -->' . PHP_EOL;
833 + $status_text .= '<figure class="wp-block-image"><img src="' . esc_url( wp_get_attachment_url( $media_id ) ) . '" alt="" class="wp-image-' . esc_attr( $media_id ) . '"/></figure>' . PHP_EOL;
834 + $status_text .= '<!-- /wp:image -->' . PHP_EOL;
835 + } elseif ( \wp_attachment_is( 'video', $media_id ) ) {
836 + $status_text .= PHP_EOL;
837 + $status_text .= '<!-- wp:video ' . wp_json_encode( array( 'id' => $media_id ) ) . ' -->' . PHP_EOL;
838 + $status_text .= '<figure class="wp-block-video"><video controls src="' . esc_url( wp_get_attachment_url( $media_id ) ) . '" width="' . esc_attr( $attachment['width'] ) . '" height="' . esc_attr( $attachment['height'] ) . '" /></figure>' . PHP_EOL;
839 + $status_text .= '<!-- /wp:video -->' . PHP_EOL;
840 + }
841 + }
842 + }
843 +
844 + $post_id = $this->send_message( $friend_user, $user_feed->get_url(), $status_text, null, $in_reply_to_id );
845 +
846 + if ( ! empty( $media_ids ) ) {
847 + foreach ( $media_ids as $media_id ) {
848 + wp_update_post(
849 + array(
850 + 'ID' => $media_id,
851 + 'post_parent' => $post_id,
852 + )
853 + );
854 + }
855 + }
856 +
857 + return apply_filters( 'mastodon_api_status', null, $post_id );
858 + }
859 +
860 + public function mastodon_api_conversation_mark_read( $post_id ) {
861 + $post = get_post( $post_id );
862 + if ( ! $post ) {
863 + return new \WP_Error( 'friends_messages_mastodon_api_conversation_mark_read', 'Record not found', array( 'status' => 404 ) );
864 + }
865 +
866 + if ( get_post_status( $post_id ) !== 'friends_read' ) {
867 + wp_update_post(
868 + array(
869 + 'ID' => $post_id,
870 + 'post_status' => 'friends_read',
871 + )
872 + );
873 + }
874 +
875 + $child_posts = get_children(
876 + array(
877 + 'post_parent' => $post_id,
878 + 'post_type' => self::CPT,
879 + 'post_status' => array( 'friends_unread' ),
880 + )
881 + );
882 + foreach ( $child_posts as $child ) {
883 + wp_update_post(
884 + array(
885 + 'ID' => $child->ID,
886 + 'post_status' => 'friends_read',
887 + )
888 + );
889 + }
890 +
891 + wp_update_post(
892 + array(
893 + 'ID' => $post_id,
894 + 'post_status' => 'friends_read',
895 + )
896 + );
897 +
898 + return true;
899 + }
900 +
901 + public function mastodon_api_status( $status, $post_id ) {
902 + if ( ! $status instanceof \Enable_Mastodon_Apps\Entity\Status || get_post_type( $post_id ) !== self::CPT ) {
903 + return $status;
904 + }
905 + $status->visibility = 'direct';
906 + $post = get_post( $post_id );
907 + if ( $post->post_parent ) {
908 + $status->in_reply_to_id = $post->post_parent;
909 + $status->in_reply_to_account_id = strval( get_current_user_id() );
910 + }
911 + return $status;
558 912 }
559 913 }