PluginProbe
Friends / 4.3.1
Friends v4.3.1
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.1, at includes/class-messages.php

893 lines 28.1 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 public function save_incoming_message( User $friend_user, $message, $subject = '', $feed_url = null, $remote_url = null, $reply_to = null ) {
166 $post_data = array(
167 'post_type' => self::CPT,
168 'post_title' => $subject,
169 'post_content' => $message,
170 'post_status' => 'friends_unread',
171 'guid' => $remote_url,
172 );
173
174 if ( $reply_to ) {
175 add_filter( 'friends_frontend_post_types', array( $this, 'friends_frontend_post_types_only_messages' ), 999 );
176 $reply_to_post_id = Feed::url_to_postid( $reply_to );
177 remove_filter( 'friends_frontend_post_types', array( $this, 'friends_frontend_post_types_only_messages' ), 999 );
178 if ( $reply_to_post_id ) {
179 $topmost_post = get_post( $reply_to_post_id );
180 while ( $topmost_post->post_parent ) {
181 $topmost_post = get_post( $topmost_post->post_parent );
182 }
183
184 $post_data['post_parent'] = $topmost_post->ID;
185 }
186 }
187
188 $post_id = $friend_user->insert_post( $post_data );
189 wp_set_post_terms( $post_id, strval( $friend_user->ID ), self::TAXONOMY );
190 if ( $feed_url ) {
191 update_post_meta( $post_id, 'friends_feed_url', $feed_url );
192 }
193
194 return $post_id;
195 }
196
197 /**
198 * Adds a message to friends_message post.
199 *
200 * @param int $post_id The return value that might be a post id.
201 * @param User $friend_user The friend user to which this should be associated.
202 * @param string $to The recipient as URL.
203 * @param string $message The message.
204 * @param string $subject The subject.
205 * @param int $reply_to_post_id The reply to post ID.
206 *
207 * @return int The post ID.
208 */
209 public function save_outgoing_message( $post_id, User $friend_user, $to, $message, $subject = '', $reply_to_post_id = null ) {
210 $content = \wpautop( $message );
211 $content = \preg_replace( '/[\n\r\t]/', '', $content );
212 $content = \trim( $content );
213
214 $post_data = array(
215 'post_type' => self::CPT,
216 'post_title' => $subject,
217 'post_content' => $content,
218 'post_status' => 'friends_read',
219 'post_parent' => $reply_to_post_id,
220 );
221
222 $post_id = wp_insert_post( $post_data );
223 wp_set_post_terms( $post_id, strval( $friend_user->ID ), self::TAXONOMY );
224 if ( $to ) {
225 update_post_meta( $post_id, 'friends_feed_url', $to );
226 }
227
228 return $post_id;
229 }
230
231 public function post_type_link( $post_link, \WP_Post $post ) {
232 if ( $post && self::CPT === $post->post_type ) {
233 if ( str_starts_with( $post->guid, home_url() ) || empty( $post->guid ) ) {
234 return home_url( '?p=' . $post->ID );
235 }
236
237 return $post->guid;
238 }
239
240 return $post_link;
241 }
242
243 /**
244 * Add the unread messages to the total.
245 *
246 * @param int $unread The unread count.
247 *
248 * @return int Unread count + unread messages.
249 */
250 public function friends_unread_messages_count( $unread ) {
251 $unread_messages = new \WP_Query(
252 array(
253 'post_type' => self::CPT,
254 'post_status' => 'friends_unread',
255 )
256 );
257 return $unread + $unread_messages->post_count;
258 }
259
260 /**
261 * Add entries to the menu for unread messages.
262 *
263 * @param \WP_Menu $wp_menu The wp menu.
264 */
265 public function friends_add_menu_unread_messages( $wp_menu ) {
266 global $post;
267 $unread_messages = new \WP_Query(
268 array(
269 'post_type' => self::CPT,
270 'post_status' => 'friends_unread',
271 )
272 );
273
274 $title = esc_html__( 'Direct Messages', 'friends' );
275 if ( $unread_messages->post_count > 0 ) {
276 $title .= ' <span class="wp-core-ui wp-ui-notification friends-open-requests">' . esc_html( number_format_i18n( $unread_messages->post_count ) ) . '</span>';
277 }
278
279 $wp_menu->add_menu(
280 array(
281 'id' => 'friends-direct-messages',
282 'parent' => 'friends-menu',
283 'title' => $title,
284 'href' => home_url( '/friends/messages/' ),
285 )
286 );
287
288 while ( $unread_messages->have_posts() ) {
289 $unread_messages->the_post();
290 $friend_user = User::get_post_author( $post );
291 $conversation = $post;
292 while ( $conversation->post_parent ) {
293 $conversation = get_post( $conversation->post_parent );
294 }
295 $conversation_url = add_query_arg( 'conversation', $conversation->ID, home_url( '/friends/messages/' ) );
296 $wp_menu->add_menu(
297 array(
298 'id' => 'friend-message-' . $friend_user->ID,
299 'parent' => 'friends-menu',
300 // translators: %s is the number of open friend requests.
301 'title' => '<span style="border-left: 2px solid #d63638; padding-left: .5em">' . esc_html( sprintf( __( 'New message from %s', 'friends' ), $friend_user->display_name ) ) . '</span>',
302 'href' => $conversation_url,
303 )
304 );
305 }
306 }
307
308 /**
309 * Ajax function to mark a message as read.
310 *
311 * @return \WP_Error The wp error.
312 */
313 public function mark_message_read() {
314 check_ajax_referer( 'friends-mark-read' );
315
316 if ( ! is_user_logged_in() ) {
317 return new \WP_Error( 'unauthorized', 'You are not authorized to send a reaction.' );
318 }
319
320 if ( ! isset( $_POST['post_id'] ) ) {
321 wp_send_json_error(
322 array(
323 'result' => false,
324 )
325 );
326 }
327 if ( ! is_numeric( $_POST['post_id'] ) || $_POST['post_id'] <= 0 ) {
328 wp_send_json_error(
329 array(
330 'result' => false,
331 )
332 );
333 }
334 $result = array();
335 $post_id = intval( $_POST['post_id'] );
336 if ( get_post_status( $post_id ) !== 'friends_read' ) {
337 wp_update_post(
338 array(
339 'ID' => $post_id,
340 'post_status' => 'friends_read',
341 )
342 );
343 $result[] = $post_id;
344 }
345 $child_posts = get_children(
346 array(
347 'post_parent' => $post_id,
348 'post_type' => self::CPT,
349 'post_status' => array( 'friends_unread' ),
350 )
351 );
352 foreach ( $child_posts as $child ) {
353 wp_update_post(
354 array(
355 'ID' => $child->ID,
356 'post_status' => 'friends_read',
357 )
358 );
359 $result[] = $child->ID;
360 }
361
362 do_action( 'friends_message_read', $post_id );
363
364 wp_send_json_success(
365 array(
366 'result' => $result,
367 )
368 );
369 }
370
371 /**
372 * Ajax function to fetch delivery status for outgoing direct messages.
373 */
374 public function get_message_delivery_statuses() {
375 check_ajax_referer( 'friends-message-delivery-statuses' );
376
377 if ( ! is_user_logged_in() ) {
378 wp_send_json_error(
379 array(
380 'message' => __( 'You are not authorized to view message delivery statuses.', 'friends' ),
381 ),
382 403
383 );
384 }
385
386 $post_ids = array();
387 if ( isset( $_POST['post_ids'] ) && is_array( $_POST['post_ids'] ) ) {
388 $post_ids = array_map( 'absint', wp_unslash( $_POST['post_ids'] ) );
389 }
390
391 $post_ids = array_filter( array_unique( $post_ids ) );
392 if ( empty( $post_ids ) ) {
393 wp_send_json_success( array( 'statuses' => array() ) );
394 }
395
396 $statuses = array();
397 foreach ( $post_ids as $post_id ) {
398 $post = get_post( $post_id );
399 if ( ! $post || self::CPT !== $post->post_type || get_current_user_id() !== intval( $post->post_author ) ) {
400 continue;
401 }
402
403 $delivery = null;
404 if ( class_exists( 'Friends\Feed_Parser_ActivityPub' ) ) {
405 $delivery = Feed_Parser_ActivityPub::get_direct_message_delivery_status( $post );
406 }
407
408 if ( ! $delivery ) {
409 continue;
410 }
411
412 $statuses[ $post_id ] = array(
413 'status' => sanitize_html_class( $delivery['status'] ),
414 'label' => $delivery['label'],
415 'title' => $delivery['title'],
416 );
417 }
418
419 wp_send_json_success( array( 'statuses' => $statuses ) );
420 }
421
422 /**
423 * Extend the author header.
424 *
425 * @param User $friend_user The friend user.
426 * @param array $args The arguments.
427 */
428 public function friends_author_header( User $friend_user, $args ) {
429 if ( apply_filters( 'friends_message_form_accounts', array(), $friend_user ) ) {
430 Friends::template_loader()->get_template_part(
431 'frontend/messages/author-header',
432 null,
433 $args
434 );
435
436 }
437 }
438
439 /**
440 * Display received messages by the friend user.
441 *
442 * @param array $args The arguments.
443 */
444 public function friends_display_messages( $args ) {
445 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
446 return;
447 }
448
449 if ( apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] ) ) {
450 $args['existing_messages'] = new \WP_Query();
451 $args['existing_messages']->set( 'post_type', self::CPT );
452 $args['existing_messages']->set( 'post_parent', '0' );
453 $args['existing_messages']->set( 'post_status', array( 'friends_read', 'friends_unread' ) );
454 $tax_query = array();
455 $tax_query[] =
456 array(
457 'taxonomy' => self::TAXONOMY,
458 'field' => 'slug',
459 'terms' => $args['friend_user']->ID,
460 );
461 $args['existing_messages']->set( 'tax_query', $tax_query );
462 $args['existing_messages']->set( 'posts_per_page', -1 );
463 if ( ! $args['existing_messages']->get_posts() ) {
464 return;
465 }
466 $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
467
468 add_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
469
470 Friends::template_loader()->get_template_part(
471 'frontend/messages/friend',
472 null,
473 $args
474 );
475
476 remove_filter( 'excerpt_length', array( $this, 'friends_message_excerpt_length' ) );
477
478 }
479 }
480
481 public function friends_message_excerpt_length() {
482 return 10;
483 }
484
485 /**
486 * Embed the message form for the friend user.
487 *
488 * @param array $args The arguments.
489 */
490 public function friends_message_form( $args ) {
491 if ( ! isset( $args['friend_user'] ) || ! $args['friend_user'] ) {
492 return;
493 }
494
495 $args['blocks-everywhere'] = false;
496 $args['accounts'] = apply_filters( 'friends_message_form_accounts', array(), $args['friend_user'] );
497
498 if ( $args['accounts'] ) {
499 Friends::template_loader()->get_template_part(
500 'frontend/messages/message-form',
501 null,
502 $args
503 );
504
505 }
506 }
507
508 /**
509 * Sends a message to a friend.
510 *
511 * @param User $friend_user The friend user.
512 * @param string $to The recipient as URL.
513 * @param string $message The message.
514 * @param string $subject The subject.
515 * @param int $reply_to_post_id The reply to post ID.
516 *
517 * @return \WP_Error|int An error or the message post id.
518 */
519 public function send_message( User $friend_user, $to, $message, $subject = '', $reply_to_post_id = null ) {
520 $tos = apply_filters( 'friends_message_form_accounts', array(), $friend_user );
521 if ( ! isset( $tos[ $to ] ) ) {
522 return new \WP_Error( 'not-a-friend', __( 'You cannot send messages to this user.', 'friends' ) );
523 }
524 if ( ! $message || ! trim( $message ) ) {
525 return new \WP_Error( 'empty-message', __( 'You cannot send empty messages.', 'friends' ) );
526 }
527
528 $post_id = apply_filters( 'friends_send_direct_message', null, $friend_user, $to, $message, $subject, $reply_to_post_id );
529
530 return $post_id;
531 }
532
533 /**
534 * Delete a conversation
535 *
536 * @param int $parent_post_id The parent post id.
537 *
538 * @return \WP_Error|\WP_Post|bool The post or false.
539 */
540 public function delete_conversation( $parent_post_id ) {
541 if ( ! current_user_can( 'delete_post', $parent_post_id ) ) {
542 return new \WP_Error( 'cannot_delete', __( 'You cannot delete converations.', 'friends' ) );
543 }
544
545 $post = get_post( $parent_post_id );
546 if ( ! $post ) {
547 return new \WP_Error( 'friends_messages_delete_conversation', 'Record not found', array( 'status' => 404 ) );
548 }
549
550 $child_posts = get_children(
551 array(
552 'post_parent' => $parent_post_id,
553 'post_type' => self::CPT,
554 'post_status' => array( 'friends_read', 'friends_unread' ),
555 )
556 );
557
558 foreach ( $child_posts as $child_post ) {
559 wp_delete_post( $child_post->ID );
560 }
561
562 wp_delete_post( $parent_post_id );
563
564 return true;
565 }
566
567 /**
568 * Handle the message form.
569 */
570 public function handle_message_send() {
571 if ( ! isset( $_REQUEST['friends_message_recipient'] ) ) {
572 return;
573 }
574
575 if ( isset( $_REQUEST['friends_message_delete_conversation'] ) ) {
576 return $this->handle_conversation_delete();
577 }
578
579 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
580 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
581 }
582
583 $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
584
585 $subject = '';
586 if ( isset( $_REQUEST['friends_message_subject'] ) ) {
587 $subject = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_subject'] ) );
588 }
589 $message = '';
590 if ( isset( $_REQUEST['friends_message_message'] ) ) {
591 $message = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_message'] ) );
592 }
593 if ( ! trim( $message ) ) {
594 wp_die( esc_html__( 'You cannot send an empty message.', 'friends' ) );
595 }
596
597 $reply_to_post_id = null;
598 if ( isset( $_REQUEST['friends_message_reply_to'] ) ) {
599 $reply_to_post_id = intval( $_REQUEST['friends_message_reply_to'] );
600 }
601
602 $to = null;
603 if ( isset( $_REQUEST['friends_message_account'] ) ) {
604 $to = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_account'] ) );
605 }
606
607 if ( ! $to ) {
608 wp_die( esc_html__( 'You cannot send a message without a recipient.', 'friends' ) );
609 }
610
611 $error = $this->send_message( $friend_user, $to, $message, $subject, $reply_to_post_id );
612
613 if ( is_wp_error( $error ) ) {
614 wp_die( esc_html( $error->get_error_message() ) );
615 }
616
617 if ( isset( $_REQUEST['friends_message_redirect_to'] ) ) {
618 $redirect_to = wp_validate_redirect( sanitize_url( wp_unslash( $_REQUEST['friends_message_redirect_to'] ) ), $friend_user->get_local_friends_page_url() );
619 } else {
620 $redirect_to = $friend_user->get_local_friends_page_url();
621 }
622
623 wp_safe_redirect( $redirect_to );
624 exit;
625 }
626
627 /**
628 * Handle the deletion of the conversation.
629 */
630 public function handle_conversation_delete() {
631 if ( ! isset( $_REQUEST['friends_message_delete_conversation'] ) || ! isset( $_REQUEST['friends_message_recipient'] ) ) {
632 return;
633 }
634
635 if ( ! isset( $_REQUEST['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'friends_send_message' ) ) {
636 wp_die( esc_html__( 'Invalid nonce.', 'friends' ) );
637 }
638
639 $friend_user = User::get_by_username( sanitize_text_field( wp_unslash( $_REQUEST['friends_message_recipient'] ) ) );
640
641 $post_id = '';
642 if ( isset( $_REQUEST['friends_message_reply_to'] ) ) {
643 $post_id = sanitize_text_field( wp_unslash( $_REQUEST['friends_message_reply_to'] ) );
644 }
645 if ( ! $post_id ) {
646 wp_die( esc_html__( 'You cannot delete a conversation without a post id.', 'friends' ) );
647 }
648 $error = $this->delete_conversation( $post_id );
649
650 if ( is_wp_error( $error ) ) {
651 wp_die( esc_html( $error->get_error_message() ) );
652 }
653
654 if ( isset( $_REQUEST['friends_message_redirect_to'] ) ) {
655 $redirect_to = wp_validate_redirect( sanitize_url( wp_unslash( $_REQUEST['friends_message_redirect_to'] ) ), $friend_user->get_local_friends_page_url() );
656 } else {
657 $redirect_to = $friend_user->get_local_friends_page_url();
658 }
659
660 wp_safe_redirect( $redirect_to );
661 exit;
662 }
663
664 public function mastodon_api_conversation( $conversation, $post_id ) {
665 $message = get_post( $post_id );
666 if ( ! $message ) {
667 return new \WP_Error( 'friends_messages_mastodon_api_conversation', 'Record not found', array( 'status' => 404 ) );
668 }
669
670 $last_status = get_posts(
671 array(
672 'post_type' => self::CPT,
673 'post_parent' => $message->ID,
674 'post_status' => array( 'friends_read', 'friends_unread' ),
675 'orderby' => 'date',
676 'order' => 'DESC',
677 'numberposts' => 1,
678 )
679 );
680 if ( ! $last_status ) {
681 $last_status = $message;
682 } else {
683 $last_status = $last_status[0];
684 }
685
686 $unread = 'friends_unread' === $message->post_status;
687 if ( ! $unread ) {
688 $unread_posts = get_children(
689 array(
690 'post_parent' => $message->ID,
691 'post_type' => self::CPT,
692 'post_status' => array( 'friends_unread' ),
693 )
694 );
695 if ( $unread_posts ) {
696 $unread = true;
697 }
698 }
699 $conversation = new \Enable_Mastodon_Apps\Entity\Conversation();
700 $conversation->id = $message->ID;
701 $conversation->unread = $unread;
702 $conversation->last_status = apply_filters( 'mastodon_api_status', null, $last_status->ID );
703 $conversation->accounts = array();
704 $account_id = $message->post_author;
705 if ( ! $account_id ) {
706 $account_id = get_post_meta( $message->ID, 'friends_feed_url', true );
707 }
708 $account = apply_filters( 'mastodon_api_account', null, $account_id );
709 if ( $account ) {
710 $conversation->accounts[] = $account;
711 }
712
713 return $conversation;
714 }
715
716 public function mastodon_api_conversations( $conversations, $user_id, $limit = 20 ) {
717 $messages = new \WP_Query();
718 $messages->set( 'post_type', self::CPT );
719 $messages->set( 'post_parent', '0' );
720 $messages->set( 'post_status', array( 'friends_read', 'friends_unread' ) );
721 $messages->set( 'posts_per_page', $limit );
722 $messages->set( 'order', 'DESC' );
723
724 foreach ( $messages->get_posts() as $message ) {
725 $conversation = $this->mastodon_api_conversation( null, $message->ID );
726 if ( $conversation && ! is_wp_error( $conversation ) ) {
727 $conversations[] = $conversation;
728 }
729 }
730
731 return $conversations;
732 }
733
734 public function api_status_context_post_types( $post_types, $post_id ) {
735 if ( self::CPT === get_post_type( $post_id ) ) {
736 return array( self::CPT );
737 }
738 return $post_types;
739 }
740
741 public function api_status_context_post_statuses( $post_statuses, $post_id ) {
742 if ( self::CPT === get_post_type( $post_id ) ) {
743 return array( 'friends_read', 'friends_unread' );
744 }
745 return $post_statuses;
746 }
747
748 public function mastodon_api_submit_status( $status, $status_text, $in_reply_to_id, $media_ids, $post_format, $visibility ) {
749 if ( $status instanceof \WP_Error || $status instanceof \Enable_Mastodon_Apps\Entity\Status || 'direct' !== $visibility ) {
750 return $status;
751 }
752
753 $mentions = apply_filters(
754 'activitypub_extract_mentions',
755 array(),
756 $status_text,
757 null
758 );
759
760 /**
761 * Documented in Enable_Mastodon_Apps\Handler\Status\prepare_post_data()
762 */
763 $status_text = apply_filters( 'mastodon_api_submit_status_text', $status_text, $in_reply_to_id, $visibility );
764
765 $friend_user = false;
766 foreach ( $mentions as $mention ) {
767 $user_feed = User_Feed::get_by_url( $mention );
768 if ( $user_feed ) {
769 $friend_user = $user_feed->get_friend_user();
770 break;
771 }
772 if ( class_exists( Feed_Parser_ActivityPub::class ) ) {
773 $user_feed = User_Feed::get_by_url( $mention );
774 $url = Feed_Parser_ActivityPub::friends_webfinger_resolve( $mention, $mention );
775 $user_feed = User_Feed::get_by_url( $url );
776 if ( $user_feed ) {
777 $friend_user = $user_feed->get_friend_user();
778 break;
779 }
780 }
781 }
782
783 if ( ! $friend_user ) {
784 // we cannot find the mentioned user, we need to stop this.
785 // TODO: handle the non-ActivityPub case.
786 return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Direct message without a known mentioned user', array( 'status' => 400 ) );
787
788 }
789
790 $app = \Enable_Mastodon_Apps\Mastodon_App::get_current_app();
791 if ( ! $app->get_disable_blocks() ) {
792 $status_text = \Enable_Mastodon_Apps\Handler\Status::convert_to_blocks( $status_text );
793 }
794
795 if ( ! empty( $media_ids ) ) {
796 foreach ( $media_ids as $media_id ) {
797 $media = get_post( $media_id );
798 if ( ! $media ) {
799 return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Media not found', array( 'status' => 400 ) );
800 }
801 if ( 'attachment' !== $media->post_type ) {
802 return new \WP_Error( 'friends_messages_mastodon_api_submit_status', 'Media not found', array( 'status' => 400 ) );
803 }
804 $attachment = \wp_get_attachment_metadata( $media_id );
805 if ( \wp_attachment_is( 'image', $media_id ) ) {
806 $status_text .= PHP_EOL;
807 $meta_json = array(
808 'id' => intval( $media_id ),
809 'sizeSlug' => 'large',
810 );
811 $status_text .= '<!-- wp:image ' . wp_json_encode( $meta_json ) . ' -->' . PHP_EOL;
812 $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;
813 $status_text .= '<!-- /wp:image -->' . PHP_EOL;
814 } elseif ( \wp_attachment_is( 'video', $media_id ) ) {
815 $status_text .= PHP_EOL;
816 $status_text .= '<!-- wp:video ' . wp_json_encode( array( 'id' => $media_id ) ) . ' -->' . PHP_EOL;
817 $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;
818 $status_text .= '<!-- /wp:video -->' . PHP_EOL;
819 }
820 }
821 }
822
823 $post_id = $this->send_message( $friend_user, $user_feed->get_url(), $status_text, null, $in_reply_to_id );
824
825 if ( ! empty( $media_ids ) ) {
826 foreach ( $media_ids as $media_id ) {
827 wp_update_post(
828 array(
829 'ID' => $media_id,
830 'post_parent' => $post_id,
831 )
832 );
833 }
834 }
835
836 return apply_filters( 'mastodon_api_status', null, $post_id );
837 }
838
839 public function mastodon_api_conversation_mark_read( $post_id ) {
840 $post = get_post( $post_id );
841 if ( ! $post ) {
842 return new \WP_Error( 'friends_messages_mastodon_api_conversation_mark_read', 'Record not found', array( 'status' => 404 ) );
843 }
844
845 if ( get_post_status( $post_id ) !== 'friends_read' ) {
846 wp_update_post(
847 array(
848 'ID' => $post_id,
849 'post_status' => 'friends_read',
850 )
851 );
852 }
853
854 $child_posts = get_children(
855 array(
856 'post_parent' => $post_id,
857 'post_type' => self::CPT,
858 'post_status' => array( 'friends_unread' ),
859 )
860 );
861 foreach ( $child_posts as $child ) {
862 wp_update_post(
863 array(
864 'ID' => $child->ID,
865 'post_status' => 'friends_read',
866 )
867 );
868 }
869
870 wp_update_post(
871 array(
872 'ID' => $post_id,
873 'post_status' => 'friends_read',
874 )
875 );
876
877 return true;
878 }
879
880 public function mastodon_api_status( $status, $post_id ) {
881 if ( ! $status instanceof \Enable_Mastodon_Apps\Entity\Status || get_post_type( $post_id ) !== self::CPT ) {
882 return $status;
883 }
884 $status->visibility = 'direct';
885 $post = get_post( $post_id );
886 if ( $post->post_parent ) {
887 $status->in_reply_to_id = $post->post_parent;
888 $status->in_reply_to_account_id = strval( get_current_user_id() );
889 }
890 return $status;
891 }
892 }
893