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
friends / includes / class-messages.php

class-messages.php in Friends 4.3.2, at includes/class-messages.php

914 lines 28.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Messages
4 *
5 * This contains the functions for Messages.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the Messages part of the Friends Plugin.
14 *
15 * @since 0.21
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Messages {
21 const CPT = 'friend_message';
22 const TAXONOMY = 'friend_message_user';
23
24 /**
25 * Contains a reference to the Friends class.
26 *
27 * @var Friends
28 */
29 private $friends;
30
31 /**
32 * Constructor
33 *
34 * @param Friends $friends A reference to the Friends object.
35 */
36 public function __construct( Friends $friends ) {
37 $this->friends = $friends;
38 $this->register_hooks();
39 }
40
41 /**
42 * Register the WordPress hooks
43 */
44 private function register_hooks() {
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 );
48 add_filter( 'friends_unread_count', array( $this, 'friends_unread_messages_count' ) );
49 add_action( 'friends_own_site_menu_top', array( $this, 'friends_add_menu_unread_messages' ) );
50 add_action( 'wp_ajax_friends-mark-read', array( $this, 'mark_message_read' ) );
51 add_action( 'wp_ajax_friends-get-message-delivery-statuses', array( $this, 'get_message_delivery_statuses' ) );
52 add_action( 'friends_author_header', array( $this, 'friends_author_header' ), 10, 2 );
53 add_action( 'friends_after_header', array( $this, 'friends_display_messages' ), 10, 2 );
54 add_action( 'friends_after_header', array( $this, 'friends_message_form' ), 11, 2 );
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 );
68 }
69
70 /**
71 * Registers the custom post type
72 */
73 public function register_custom_post_type() {
74 $labels = array(
75 'name' => __( 'Friend Messages', 'friends' ),
76 'singular_name' => __( 'Friend Message', 'friends' ),
77 'add_new' => _x( 'Add New', 'cached friend post', 'friends' ),
78 'add_new_item' => __( 'Add New Friend Message', 'friends' ),
79 'edit_item' => __( 'Edit Friend Message', 'friends' ),
80 'new_item' => __( 'New Friend Message', 'friends' ),
81 'all_items' => __( 'All Friend Messages', 'friends' ),
82 'view_item' => __( 'View Friend Message', 'friends' ),
83 'search_items' => __( 'Search Friend Messages', 'friends' ),
84 'not_found' => __( 'No Friend Messages found', 'friends' ),
85 'not_found_in_trash' => __( 'No Friend Messages found in the Trash', 'friends' ),
86 'parent_item_colon' => '',
87 'menu_name' => __( 'Friend Messages', 'friends' ),
88 );
89
90 $args = array(
91 'labels' => $labels,
92 'description' => "A friend's message",
93 'publicly_queryable' => Friends::has_required_privileges(),
94 'show_ui' => true,
95 'show_in_menu' => apply_filters( 'friends_show_cached_posts', false ),
96 'show_in_nav_menus' => false,
97 'show_in_admin_bar' => false,
98 'show_in_rest' => Friends::has_required_privileges(),
99 'exclude_from_search' => true,
100 'public' => false,
101 'delete_with_user' => true,
102 'menu_position' => 6,
103 'menu_icon' => 'dashicons-admin-comments',
104 'supports' => array( 'title', 'editor', 'author', 'revisions' ),
105 'taxonomies' => array( 'friend-reaction-' . get_current_user_id() ),
106 'has_archive' => true,
107 'rewrite' => false,
108 );
109
110 register_post_type( self::CPT, $args );
111
112 register_post_status(
113 'friends_unread',
114 array(
115 'label' => _x( 'Unread', 'message', 'friends' ),
116 'public' => false,
117 'exclude_from_search' => true,
118 'show_in_admin_all_list' => false,
119 'show_in_admin_status_list' => false,
120 // translators: %s; number of unread messages.
121 'label_count' => _n_noop( 'Unread (%s)', 'Unread (%s)', 'friends' ),
122 )
123 );
124
125 register_post_status(
126 'friends_read',
127 array(
128 'label' => _x( 'Read', 'message', 'friends' ),
129 'public' => false,
130 'exclude_from_search' => true,
131 'show_in_admin_all_list' => false,
132 'show_in_admin_status_list' => false,
133 // translators: %s; number of read messages.
134 'label_count' => _n_noop( 'Read (%s)', 'Read (%s)', 'friends' ),
135 )
136 );
137 }
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 );
154 }
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
164 /**
165 * Look up a direct message by its URL.
166 *
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.
173 */
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 }
207 }
208
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 );
213 }
214
215 return $post_id;
216 }
217
218 /**
219 * Adds a message to friends_message post.
220 *
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.
227 *
228 * @return int The post ID.
229 */
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,
241 );
242
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 );
247 }
248
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;
259 }
260
261 return $post_link;
262 }
263
264 /**
265 * Add the unread messages to the total.
266 *
267 * @param int $unread The unread count.
268 *
269 * @return int Unread count + unread messages.
270 */
271 public function friends_unread_messages_count( $unread ) {
272 $unread_messages = new \WP_Query(
273 array(
274 'post_type' => self::CPT,
275 'post_status' => 'friends_unread',
276 )
277 );
278 return $unread + $unread_messages->post_count;
279 }
280
281 /**
282 * Add entries to the menu for unread messages.
283 *
284 * @param \WP_Menu $wp_menu The wp menu.
285 */
286 public function friends_add_menu_unread_messages( $wp_menu ) {
287 global $post;
288 $unread_messages = new \WP_Query(
289 array(
290 'post_type' => self::CPT,
291 'post_status' => 'friends_unread',
292 )
293 );
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
309 while ( $unread_messages->have_posts() ) {
310 $unread_messages->the_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/' ) );
317 $wp_menu->add_menu(
318 array(
319 'id' => 'friend-message-' . $friend_user->ID,
320 'parent' => 'friends-menu',
321 // translators: %s is the number of open friend requests.
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>',
323 'href' => $conversation_url,
324 )
325 );
326 }
327 }
328
329 /**
330 * Ajax function to mark a message as read.
331 *
332 * @return \WP_Error The wp error.
333 */
334 public function mark_message_read() {
335 check_ajax_referer( 'friends-mark-read' );
336
337 if ( ! is_user_logged_in() ) {
338 return new \WP_Error( 'unauthorized', 'You are not authorized to send a reaction.' );
339 }
340
341 if ( ! isset( $_POST['post_id'] ) ) {
342 wp_send_json_error(
343 array(
344 'result' => false,
345 )
346 );
347 }
348 if ( ! is_numeric( $_POST['post_id'] ) || $_POST['post_id'] <= 0 ) {
349 wp_send_json_error(
350 array(
351 'result' => false,
352 )
353 );
354 }
355 $result = array();
356 $post_id = intval( $_POST['post_id'] );
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(
367 array(
368 'post_parent' => $post_id,
369 'post_type' => self::CPT,
370 'post_status' => array( 'friends_unread' ),
371 )
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 }
382
383 do_action( 'friends_message_read', $post_id );
384
385 wp_send_json_success(
386 array(
387 'result' => $result,
388 )
389 );
390 }
391
392 /**
393 * Ajax function to fetch delivery status for outgoing direct messages.
394 */
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 ) );
441 }
442
443 /**
444 * Extend the author header.
445 *
446 * @param User $friend_user The friend user.
447 * @param array $args The arguments.
448 */
449 public function friends_author_header( User $friend_user, $args ) {
450 if ( apply_filters( 'friends_message_form_accounts', array(), $friend_user ) ) {
451 Friends::template_loader()->get_template_part(
452 'frontend/messages/author-header',
453 null,
454 $args
455 );
456
457 }
458 }
459
460 /**
461 * Display received messages by the friend user.
462 *
463 * @param array $args The arguments.
464 */
465 public function friends_display_messages( $args ) {
466 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
467 return;
468 }
469
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[] =
477 array(
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() ) {
485 return;
486 }
487 $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
488
489 add_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
490
491 Friends::template_loader()->get_template_part(
492 'frontend/messages/friend',
493 null,
494 $args
495 );
496
497 remove_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
498
499 }
500 }
501
502 public function friends_message_excerpt_length() {
503 return 10;
504 }
505
506 /**
507 * Embed the message form for the friend user.
508 *
509 * @param array $args The arguments.
510 */
511 public function friends_message_form( $args ) {
512 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
513 return;
514 }
515
516 $args['blocks-everywhere'] = false;
517 $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
518
519 if ( $args['accounts'] ) {
520 Friends::template_loader()->get_template_part(
521 'frontend/messages/message-form',
522 null,
523 $args
524 );
525
526 }
527 }
528
529 /**
530 * Sends a message to a friend.
531 *
532 * @param User $friend_user The friend user.
533 * @param string $to The recipient as URL.
534 * @param string $message The message.
535 * @param string $subject The subject.
536 * @param int $reply_to_post_id The reply to post ID.
537 *
538 * @return \WP_Error|int An error or the message post id.
539 */
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 ] ) ) {
543 return new \WP_Error( 'not-a-friend', __( 'You cannot send messages to this user.', 'friends' ) );
544 }
545 if ( ! $message || ! trim( $message ) ) {
546 return new \WP_Error( 'empty-message', __( 'You cannot send empty messages.', 'friends' ) );
547 }
548
549 $post_id = apply_filters( 'friends_send_direct_message', null, $friend_user, $to, $message, $subject, $reply_to_post_id );
550
551 return $post_id;
552 }
553
554 /**
555 * Delete a conversation
556 *
557 * @param int $parent_post_id The parent post id.
558 *
559 * @return \WP_Error|\WP_Post|bool The post or false.
560 */
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' ) );
564 }
565
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(
572 array(
573 'post_parent' => $parent_post_id,
574 'post_type' => self::CPT,
575 'post_status' => array( 'friends_read', 'friends_unread' ),
576 )
577 );
578
579 foreach ( $child_posts as $child_post ) {
580 wp_delete_post( $child_post->ID );
581 }
582
583 wp_delete_post( $parent_post_id );
584
585 return true;
586 }
587
588 /**
589 * Handle the message form.
590 */
591 public function handle_message_send() {
592 if ( ! isset( $_REQUEST['friends_message_recipient'] ) ) {
593 return;
594 }
595
596 if ( isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
597 return $this->handle_conversation_delete();
598 }
599
600 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
601 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
602 }
603
604 $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
605
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 }
617
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 }
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
634 if ( is_wp_error( $error ) ) {
635 wp_die( esc_html( $error->get_error_message() ) );
636 }
637
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 );
645 exit;
646 }
647
648 /**
649 * Handle the deletion of the conversation.
650 */
651 public function handle_conversation_delete() {
652 if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) || ! isset( $_REQUEST['friends_message_recipient'] ) ) {
653 return;
654 }
655
656 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
657 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
658 }
659
660 $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
661
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 );
670
671 if ( is_wp_error( $error ) ) {
672 wp_die( esc_html( $error->get_error_message() ) );
673 }
674
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 );
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;
912 }
913 }
914