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 -188 2.7.64.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() {
@@ -119,125 +133,133 @@
119 133 // translators: %s; number of read messages.
120 134 'label_count' => _n_noop( 'Read (%s)', 'Read (%s)', 'friends' ),
121 135 )
122 136 );
137 + }
123 138
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,
152 + );
153 + register_taxonomy( self::TAXONOMY, self::CPT, $args );
124 154 }
125 155
126 - /**
127 - * Add the REST API to send and receive friend requests
128 - */
129 - public function add_rest_routes() {
130 - register_rest_route(
131 - REST::PREFIX,
132 - 'message',
133 - array(
134 - 'methods' => 'POST',
135 - 'callback' => array( $this, 'rest_receive_message' ),
136 - 'permission_callback' => Friends::authenticated_for_posts(),
137 - )
138 - );
156 +
157 +
158 +
159 + public function friends_frontend_post_types_only_messages( $post_types ) {
160 + $post_types = array( self::CPT );
161 + return $post_types;
139 162 }
140 163
141 164 /**
142 - * Receive a message via REST
165 + * Look up a direct message by its URL.
143 166 *
144 - * @param \WP_REST_Request $request The incoming request.
145 - * @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.
146 173 */
147 - public function rest_receive_message( \WP_REST_Request $request ) {
148 - $tokens = explode( '-', $request->get_param( 'auth' ) );
149 - $user_id = $this->friends->access_control->verify_token( $tokens[0], isset( $tokens[1] ) ? $tokens[1] : null, isset( $tokens[2] ) ? $tokens[2] : null );
150 - if ( ! $user_id ) {
151 - return new \WP_Error(
152 - 'friends_request_failed',
153 - __( 'Could not respond to the request.', 'friends' ),
154 - array(
155 - 'status' => 403,
156 - )
157 - );
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 + }
158 207 }
159 208
160 - $friend_user = new User( $user_id );
161 - if ( ! $friend_user->has_cap( self::get_minimum_cap() ) ) {
162 - return new \WP_Error(
163 - 'friends_request_failed',
164 - __( 'Could not respond to the request.', 'friends' ),
165 - array(
166 - 'status' => 403,
167 - )
168 - );
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 );
169 213 }
170 214
171 - $subject = wp_unslash( $request->get_param( 'subject' ) );
172 - $message = wp_unslash( $request->get_param( 'message' ) );
173 - $this->add_message_to_post( $friend_user, $friend_user, $subject, $message, true );
174 -
175 - do_action( 'notify_friend_message_received', $friend_user, $message, $subject );
176 -
177 - return array(
178 - 'status' => 'message-received',
179 - );
215 + return $post_id;
180 216 }
181 217
182 218 /**
183 219 * Adds a message to friends_message post.
184 220 *
185 - * @param \WP_User $sender The sender.
186 - * @param User $friend_user The friend user to which this should be associated.
187 - * @param string $subject The subject.
188 - * @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.
189 227 *
190 228 * @return int The post ID.
191 229 */
192 - private function add_message_to_post( \WP_User $sender, User $friend_user, $subject, $message ) {
193 - $existing_message = new \WP_Query(
194 - array(
195 - 'post_type' => self::CPT,
196 - 'author' => $friend_user->ID,
197 - 'title' => $subject,
198 - 'post_status' => array( 'friends_read', 'friends_unread' ),
199 - )
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,
200 241 );
201 - $mark_unread = $sender->ID === $friend_user->ID;
202 242
203 - $content = '';
204 - $content .= '<!-- wp:friends/message {"sender":' . $sender->ID . ',"date":' . time() . '} -->' . PHP_EOL;
205 - $content .= '<div class="wp-block-friends-message">';
206 - $content .= '<span class="date">' . esc_html( gmdate( 'Y-m-d H:i:s' ) ) . '</span> ';
207 - $content .= '<strong>' . esc_html( $sender->display_name ) . '</strong>: ';
208 - if ( false === strpos( $message, '<!-- wp:' ) ) {
209 - $content .= '<!-- wp:paragraph -->' . PHP_EOL . '<p>' . wp_kses_post( $message );
210 - $content .= '</p>' . PHP_EOL;
211 - $content .= '<!-- /wp:paragraph -->';
212 - } else {
213 - $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 );
214 247 }
215 - $content .= '</div>';
216 - $content .= '<!-- /wp:friends/message -->';
217 248
218 - if ( $existing_message->have_posts() ) {
219 - $post = $existing_message->next_post();
220 - $post_id = $post->ID;
221 - wp_update_post(
222 - array(
223 - 'ID' => $post->ID,
224 - 'post_content' => $post->post_content . PHP_EOL . $content,
225 - 'post_status' => $mark_unread ? 'friends_unread' : 'friends_read',
226 - )
227 - );
228 - } else {
229 - $post_id = $friend_user->insert_post(
230 - array(
231 - 'post_type' => self::CPT,
232 - 'post_title' => $subject,
233 - 'post_content' => $content,
234 - 'post_status' => $mark_unread ? 'friends_unread' : 'friends_read',
235 - )
236 - );
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;
237 259 }
238 260
239 - return $post_id;
261 + return $post_link;
240 262 }
241 263
242 264 /**
243 265 * Add the unread messages to the total.
@@ -269,11 +291,30 @@
269 291 'post_status' => 'friends_unread',
270 292 )
271 293 );
272 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 +
273 309 while ( $unread_messages->have_posts() ) {
274 310 $unread_messages->the_post();
275 - $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/' ) );
276 317 $wp_menu->add_menu(
277 318 array(
278 319 'id' => 'friend-message-' . $friend_user->ID,
279 320 'parent' => 'friends-menu',
@@ -278,9 +319,9 @@
278 319 'id' => 'friend-message-' . $friend_user->ID,
279 320 'parent' => 'friends-menu',
280 321 // translators: %s is the number of open friend requests.
281 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>',
282 - 'href' => $friend_user->get_local_friends_page_url(),
323 + 'href' => $conversation_url,
283 324 )
284 325 );
285 326 }
286 327 }
@@ -310,36 +351,94 @@
310 351 'result' => false,
311 352 )
312 353 );
313 354 }
314 -
355 + $result = array();
315 356 $post_id = intval( $_POST['post_id'] );
316 - 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(
317 367 array(
318 - 'ID' => $post_id,
319 - 'post_status' => 'friends_read',
368 + 'post_parent' => $post_id,
369 + 'post_type' => self::CPT,
370 + 'post_status' => array( 'friends_unread' ),
320 371 )
321 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 + }
322 382
323 383 do_action( 'friends_message_read', $post_id );
324 384
325 385 wp_send_json_success(
326 386 array(
327 - 'result' => true,
387 + 'result' => $result,
328 388 )
329 389 );
330 390 }
331 391
332 392 /**
333 - * Adds the friend requests to the unread count.
334 - *
335 - * @param int $unread The unread count.
336 - *
337 - * @return int Unread count + friend requests.
393 + * Ajax function to fetch delivery status for outgoing direct messages.
338 394 */
339 - public function friends_unread_friend_request_count( $unread ) {
340 - $friend_requests = User_Query::all_friend_requests();
341 - 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 ) );
342 441 }
343 442
344 443 /**
345 444 * Extend the author header.
@@ -347,9 +446,9 @@
347 446 * @param User $friend_user The friend user.
348 447 * @param array $args The arguments.
349 448 */
350 449 public function friends_author_header( User $friend_user, $args ) {
351 - if ( $friend_user->has_cap( self::get_minimum_cap() ) ) {
450 + if ( apply_filters( 'friends_message_form_accounts', array(), $friend_user ) ) {
352 451 Friends::template_loader()->get_template_part(
353 452 'frontend/messages/author-header',
354 453 null,
355 454 $args
@@ -367,21 +466,29 @@
367 466 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
368 467 return;
369 468 }
370 469
371 - if ( $args['friend_user']->has_cap( self::get_minimum_cap() ) ) {
372 - $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[] =
373 477 array(
374 - 'post_type' => self::CPT,
375 - 'author' => $args['friend_user']->ID,
376 - 'post_status' => array( 'friends_read', 'friends_unread' ),
377 - )
378 - );
379 -
380 - 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() ) {
381 485 return;
382 486 }
487 + $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
383 488
489 + add_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
490 +
384 491 Friends::template_loader()->get_template_part(
385 492 'frontend/messages/friend',
386 493 null,
387 494 $args
@@ -386,11 +493,17 @@
386 493 null,
387 494 $args
388 495 );
389 496
497 + remove_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
498 +
390 499 }
391 500 }
392 501
502 + public function friends_message_excerpt_length() {
503 + return 10;
504 + }
505 +
393 506 /**
394 507 * Embed the message form for the friend user.
395 508 *
396 509 * @param array $args The arguments.
@@ -399,11 +512,12 @@
399 512 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
400 513 return;
401 514 }
402 515
403 - $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'] );
404 518
405 - if ( $args['friend_user']->has_cap( self::get_minimum_cap() ) ) {
519 + if ( $args['accounts'] ) {
406 520 Friends::template_loader()->get_template_part(
407 521 'frontend/messages/message-form',
408 522 null,
409 523 $args
@@ -412,64 +526,29 @@
412 526 }
413 527 }
414 528
415 529 /**
416 - * Gets the minimum capability necessary to use messages.
417 - *
418 - * @return string The minimum capability.
419 - */
420 - public static function get_minimum_cap() {
421 - return apply_filters( 'friends_message_minimum_cap', 'friend' );
422 - }
423 -
424 - /**
425 530 * Sends a message to a friend.
426 531 *
427 532 * @param User $friend_user The friend user.
533 + * @param string $to The recipient as URL.
428 534 * @param string $message The message.
429 535 * @param string $subject The subject.
536 + * @param int $reply_to_post_id The reply to post ID.
430 537 *
431 538 * @return \WP_Error|int An error or the message post id.
432 539 */
433 - public function send_message( User $friend_user, $message, $subject = null ) {
434 - 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 ] ) ) {
435 543 return new \WP_Error( 'not-a-friend', __( 'You cannot send messages to this user.', 'friends' ) );
436 544 }
437 - if ( ! trim( $message ) ) {
545 + if ( ! $message || ! trim( $message ) ) {
438 546 return new \WP_Error( 'empty-message', __( 'You cannot send empty messages.', 'friends' ) );
439 547 }
440 548
441 - if ( empty( $subject ) ) {
442 - $subject = sprintf(
443 - // translators: %1$s is a date, %2$s is a time.
444 - __( 'Conversation started on %1$s at %2$s', 'friends' ),
445 - gmdate( 'Y-m-d' ),
446 - gmdate( 'H:i:s' )
447 - );
448 - }
549 + $post_id = apply_filters( 'friends_send_direct_message', null, $friend_user, $to, $message, $subject, $reply_to_post_id );
449 550
450 - $post_id = $this->add_message_to_post( wp_get_current_user(), $friend_user, $subject, $message );
451 -
452 - $body = array(
453 - 'subject' => $subject,
454 - 'message' => $message,
455 - 'auth' => $friend_user->get_friend_auth(),
456 - );
457 -
458 - $response = wp_safe_remote_post(
459 - $friend_user->get_rest_url() . '/message',
460 - array(
461 - 'body' => $body,
462 - 'timeout' => 20,
463 - 'redirection' => 5,
464 - )
465 - );
466 -
467 - if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
468 - // TODO find a way to message the user.
469 - return new \WP_Error( 'invalid-response', __( 'We received an unexpected response to our message.', 'friends' ) );
470 - }
471 -
472 551 return $post_id;
473 552 }
474 553
475 554 /**
@@ -474,33 +553,37 @@
474 553
475 554 /**
476 555 * Delete a conversation
477 556 *
478 - * @param User $friend_user The friend user.
479 - * @param string $subject The subject.
557 + * @param int $parent_post_id The parent post id.
480 558 *
481 559 * @return \WP_Error|\WP_Post|bool The post or false.
482 560 */
483 - public function delete_conversation( User $friend_user, $subject ) {
484 - if ( ! $friend_user->has_cap( self::get_minimum_cap() ) ) {
485 - 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' ) );
486 564 }
487 565
488 - $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(
489 572 array(
573 + 'post_parent' => $parent_post_id,
490 574 'post_type' => self::CPT,
491 - 'author' => $friend_user->ID,
492 - 'title' => $subject,
493 575 'post_status' => array( 'friends_read', 'friends_unread' ),
494 576 )
495 577 );
496 578
497 - if ( $existing_message->have_posts() ) {
498 - $post = $existing_message->next_post();
499 - return wp_trash_post( $post->ID );
579 + foreach ( $child_posts as $child_post ) {
580 + wp_delete_post( $child_post->ID );
500 581 }
501 582
502 - return false;
583 + wp_delete_post( $parent_post_id );
584 +
585 + return true;
503 586 }
504 587
505 588 /**
506 589 * Handle the message form.
@@ -513,24 +596,53 @@
513 596 if ( isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
514 597 return $this->handle_conversation_delete();
515 598 }
516 599
517 - if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'friends_send_message' ) ) {
518 - 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' ) );
519 602 }
520 603
521 - $friend_user = new User( $_REQUEST['friends_message_recipient'] );
604 + $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
522 605
523 - $subject = wp_unslash( $_REQUEST['friends_message_subject'] );
524 - $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 + }
525 617
526 - $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 + }
527 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 +
528 634 if ( is_wp_error( $error ) ) {
529 635 wp_die( esc_html( $error->get_error_message() ) );
530 636 }
531 637
532 - 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 );
533 645 exit;
534 646 }
535 647
536 648 /**
@@ -536,25 +648,266 @@
536 648 /**
537 649 * Handle the deletion of the conversation.
538 650 */
539 651 public function handle_conversation_delete() {
540 - if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
652 + if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) || ! isset( $_REQUEST['friends_message_recipient'] ) ) {
541 653 return;
542 654 }
543 655
544 - if ( ! wp_verify_nonce( $_REQUEST['_wpnonce'], 'friends_send_message' ) ) {
545 - 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' ) );
546 658 }
547 659
548 - $friend_user = new User( $_REQUEST['friends_message_recipient'] );
660 + $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
549 661
550 - $subject = wp_unslash( $_REQUEST['friends_message_subject'] );
551 - $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 );
552 670
553 671 if ( is_wp_error( $error ) ) {
554 672 wp_die( esc_html( $error->get_error_message() ) );
555 673 }
556 674
557 - 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 );
558 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;
559 912 }
560 913 }