PluginProbe
Friends / 2.7.4
Friends v2.7.4
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-frontend.php

class-frontend.php in Friends 2.7.4, at includes/class-frontend.php

1,284 lines 37.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Page
4 *
5 * This contains the functions for /friends/
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the /friends/ part of the Friends Plugin.
14 *
15 * @since 0.6
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Frontend {
21 /**
22 * Contains a reference to the Friends class.
23 *
24 * @var Friends
25 */
26 private $friends;
27
28 /**
29 * Whether we are on the /friends page.
30 *
31 * @var boolean
32 */
33 private $is_friends_page = false;
34
35 /**
36 * Whether an author is being displayed
37 *
38 * @var object|false
39 */
40 public $author = false;
41
42 /**
43 * Whether a post-format is being displayed
44 *
45 * @var string|false
46 */
47 public $post_format = false;
48
49 /**
50 * Whether a reaciton is being displayed
51 *
52 * @var string|false
53 */
54 public $reaction = false;
55
56 /**
57 * Constructor
58 *
59 * @param Friends $friends A reference to the Friends object.
60 */
61 public function __construct( Friends $friends ) {
62 $this->friends = $friends;
63 $this->register_hooks();
64 }
65
66 /**
67 * Register the WordPress hooks
68 */
69 private function register_hooks() {
70 add_filter( 'pre_get_posts', array( $this, 'friend_posts_query' ), 2 );
71 add_filter( 'post_type_link', array( $this, 'friend_post_link' ), 10, 2 );
72 add_filter( 'friends_header_widget_title', array( $this, 'header_widget_title' ) );
73 add_filter( 'get_edit_post_link', array( $this, 'friend_post_edit_link' ) );
74 add_filter( 'template_include', array( $this, 'template_override' ) );
75 add_filter( 'wp_loaded', array( $this, 'add_rewrite_rule' ) );
76 add_filter( 'init', array( $this, 'register_friends_sidebar' ) );
77 add_action( 'init', array( $this, 'add_theme_supports' ) );
78 add_action( 'wp_ajax_friends_publish', array( $this, 'ajax_frontend_publish_post' ) );
79 add_action( 'wp_ajax_friends-change-post-format', array( $this, 'ajax_change_post_format' ) );
80 add_action( 'wp_ajax_friends-load-next-page', array( $this, 'ajax_load_next_page' ) );
81 add_action( 'wp_ajax_friends-autocomplete', array( $this, 'ajax_autocomplete' ) );
82 add_action( 'wp_ajax_friends-in-reply-to-preview', array( $this, 'ajax_in_reply_to_preview' ) );
83 add_action( 'wp_ajax_friends-star', array( $this, 'ajax_star_friend_user' ) );
84 add_action( 'wp_ajax_friends-load-comments', array( $this, 'ajax_load_comments' ) );
85 add_action( 'wp_ajax_friends-reblog', array( $this, 'wp_ajax_reblog' ) );
86 add_action( 'friends_post_footer_first', array( $this, 'reblog_button' ) );
87 add_filter( 'friends_reblog', array( get_called_class(), 'reblog' ), 10, 2 );
88 add_filter( 'friends_unreblog', array( get_called_class(), 'unreblog' ), 10, 2 );
89 add_action( 'wp_untrash_post_status', array( $this, 'untrash_post_status' ), 10, 3 );
90 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_scripts' ) );
91 add_action( 'wp_enqueue_scripts', array( $this, 'dequeue_scripts' ), 99999 );
92 add_action( 'wp_footer', array( $this, 'dequeue_scripts' ) );
93 add_action( 'the_post', array( $this, 'the_post' ), 10, 2 );
94 add_action( 'parse_query', array( $this, 'parse_query' ) );
95 add_filter( 'body_class', array( $this, 'add_body_class' ) );
96
97 add_filter( 'friends_override_author_name', array( $this, 'override_author_name' ), 10, 3 );
98 }
99
100 /**
101 * We're asking WordPress to handle the title for us.
102 */
103 public function add_rewrite_rule() {
104 add_rewrite_rule(
105 'friends/(.*)/(?:feed/)?(feed|rdf|rss|rss2|atom)/?$',
106 'index.php?pagename=friends/$matches[1]&feed=$matches[2]',
107 'top'
108 );
109 add_rewrite_rule(
110 'friends/(.*)/(\d+)/?$',
111 'index.php?pagename=friends/$matches[1]&page=$matches[2]',
112 'top'
113 );
114 add_rewrite_rule(
115 'friends/(.*)',
116 'index.php?pagename=friends/$matches[1]',
117 'top'
118 );
119 }
120
121 /**
122 * Run our add_theme_supports if on the frontend.
123 */
124 public function add_theme_supports() {
125 if ( ! Friends::on_frontend() ) {
126 return;
127 }
128
129 $this->add_theme_support_title_tag();
130 $this->add_theme_support_admin_bar();
131 }
132
133 /**
134 * We're asking WordPress to handle the title for us.
135 */
136 private function add_theme_support_title_tag() {
137 add_theme_support( 'title-tag' );
138 add_filter( 'document_title_parts', array( $this, 'modify_page_title' ) );
139 }
140
141 /**
142 * Remove the margin-top on the friends page.
143 */
144 private function add_theme_support_admin_bar() {
145 add_theme_support(
146 'admin-bar',
147 array(
148 'callback' => '__return_false',
149 )
150 );
151 }
152
153 /**
154 * Modify the page title to be output. This function is only invoked on the friends page.
155 *
156 * @param array $title The title.
157 *
158 * @return array The modified title.
159 */
160 public function modify_page_title( $title ) {
161 if ( is_single() ) {
162 $title['page'] = __( 'Friends', 'friends' );
163 $title['site'] = $this->author->display_name;
164 } elseif ( $this->author instanceof User ) {
165 $title['title'] = __( 'Friends', 'friends' );
166 $title['site'] = $this->author->display_name;
167 } else {
168 $title = array(
169 'site' => __( 'Friends', 'friends' ),
170 );
171 }
172 return $title;
173 }
174
175 /**
176 * Registers the sidebar for the /friends page.
177 */
178 public function register_friends_sidebar() {
179 register_sidebar(
180 array(
181 'name' => 'Friends Topbar',
182 'id' => 'friends-topbar',
183 'before_widget' => '<div class="friends-main-widget">',
184 'after_widget' => '</div>',
185 'before_title' => '<h1>',
186 'after_title' => '</h1>',
187 )
188 );
189 register_sidebar(
190 array(
191 'name' => 'Friends Sidebar',
192 'id' => 'friends-sidebar',
193 'before_widget' => '<div class="friends-widget">',
194 'after_widget' => '</div>',
195 'before_title' => '<h5>',
196 'after_title' => '</h5>',
197 )
198 );
199
200 if ( Friends::on_frontend() ) {
201 add_action( 'customize_register', '__return_true' );
202 }
203 }
204
205 /**
206 * Reference our script for the /friends page
207 */
208 public function enqueue_scripts() {
209 global $wp_query;
210
211 if ( is_user_logged_in() && Friends::on_frontend() ) {
212 $handle = 'friends';
213 $file = 'friends.js';
214 $version = Friends::VERSION;
215 wp_enqueue_script( $handle, plugins_url( $file, FRIENDS_PLUGIN_FILE ), array( 'common', 'jquery', 'wp-util' ), apply_filters( 'friends_debug_enqueue', $version, $handle, dirname( FRIENDS_PLUGIN_FILE ) . '/' . $file ) );
216
217 $query_vars = serialize( $this->get_minimal_query_vars( $wp_query->query_vars ) );
218
219 $variables = array(
220 'emojis_json' => plugins_url( 'emojis.json', FRIENDS_PLUGIN_FILE ),
221 'ajax_url' => admin_url( 'admin-ajax.php' ),
222 'text_link_expired' => __( 'The link has expired. A new link has been generated, please click it again.', 'friends' ),
223 'text_undo' => __( 'Undo' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
224 'text_trash_post' => __( 'Trash this post', 'friends' ),
225 'text_del_convers' => __( 'Do you really want to delete this conversation?', 'friends' ),
226 'text_no_more_posts' => __( 'No more posts available.', 'friends' ),
227 'query_vars' => $query_vars,
228 'qv_sign' => sha1( wp_salt( 'nonce' ) . $query_vars ),
229 'current_page' => get_query_var( 'paged' ) ? get_query_var( 'paged' ) : 1,
230 'max_page' => $wp_query->max_num_pages,
231 );
232 wp_localize_script( 'friends', 'friends', $variables );
233
234 $handle = 'friends';
235 $file = 'friends.css';
236 $version = Friends::VERSION;
237 wp_enqueue_style( $handle, plugins_url( $file, FRIENDS_PLUGIN_FILE ), array(), apply_filters( 'friends_debug_enqueue', $version, $handle, dirname( FRIENDS_PLUGIN_FILE ) . '/' . $file ) );
238 }
239 }
240
241 public function dequeue_scripts() {
242 if ( is_user_logged_in() && Friends::on_frontend() ) {
243 // Dequeue theme styles so taht they don't interact with the Friends frontend.
244 $wp_styles = wp_styles();
245 foreach ( $wp_styles->queue as $style ) {
246 $src = $wp_styles->registered[ $style ]->src;
247 if ( 'global-styles' === $style || false !== strpos( $src, '/themes/' ) ) {
248 wp_dequeue_style( $style );
249 }
250 }
251 }
252 }
253
254 public function the_post( $post, $query ) {
255 Subscription::set_authordata_by_query( $query );
256 }
257
258 public function parse_query( $query ) {
259 Subscription::set_authordata_by_query( $query );
260 }
261
262 /**
263 * Add a CSS class to the body
264 *
265 * @param array $classes The existing CSS classes.
266 * @return array The modified CSS classes.
267 */
268 public function add_body_class( $classes ) {
269 if ( $this->friends->on_frontend() ) {
270 $classes[] = 'friends-page';
271 }
272
273 return $classes;
274 }
275
276 /**
277 * Gets the minimal query variables.
278 *
279 * @param array $query_vars The query variables.
280 *
281 * @return array The minimal query variables.
282 */
283 private function get_minimal_query_vars( $query_vars ) {
284 return array_filter( array_intersect_key( $query_vars, array_flip( array( 'p', 'page_id', 'pagename', 'author', 'author__not_in', 'post_type', 'post_status', 'posts_per_page', 'order', 'tax_query' ) ) ) );
285 }
286
287
288 public function wp_ajax_reblog() {
289 if ( ! current_user_can( Friends::REQUIRED_ROLE ) ) {
290 wp_send_json_error( 'error' );
291 }
292
293 $post = get_post( $_POST['post_id'] );
294 if ( ! $post || ! Friends::check_url( $post->guid ) ) {
295 wp_send_json_error( 'unknown-post', array( 'guid' => $post->guid ) );
296 }
297
298 /**
299 * Reblogs a post
300 *
301 * @param int|null $reblog_post_id The post ID of the reblogged post. Default null.
302 * @param WP_Post $post The post object.
303 */
304 $reblog_post_id = apply_filters( 'friends_reblog', null, $post );
305 if ( ! $reblog_post_id || is_wp_error( $reblog_post_id ) ) {
306 wp_send_json_error( 'error' );
307 }
308
309 wp_send_json_success(
310 array(
311 'post_id' => $reblog_post_id,
312 )
313 );
314 }
315
316 public function reblog_button() {
317 $button_label = apply_filters( 'friends_reblog_button_label', _x( 'Reblog', 'button', 'friends' ) );
318
319 Friends::template_loader()->get_template_part(
320 'frontend/parts/reblog-button',
321 null,
322 array(
323 'button-label' => $button_label,
324 )
325 );
326 }
327
328 public static function reblog( $ret, $post ) {
329 if ( ! $post ) {
330 return $ret;
331 }
332 $post = get_post( $post );
333 if ( ! $post ) {
334 return $ret;
335 }
336
337 $author = get_post_meta( $post->ID, 'author', true );
338 if ( ! $author ) {
339 $friend = User::get_post_author( $post );
340 $author = $friend->display_name;
341 }
342
343 $reblog = '<!-- wp:paragraph -->' . PHP_EOL . '<p>';
344 $reblog .= sprintf(
345 // translators: %s is a link.
346 __( 'Reblog via %s', 'friends' ),
347 '<a href="' . esc_url( $post->guid ) . '">' . esc_html( $author ) . '</a>'
348 );
349
350 $reblog .= PHP_EOL . '</p>' . PHP_EOL . '<!-- /wp:paragraph -->' . PHP_EOL;
351 $new_post = array(
352 'post_title' => $post->title,
353 'post_author' => get_current_user_id(),
354 'post_status' => 'publish',
355 'post_type' => 'post',
356 'post_content' => $reblog . $post->post_content,
357 );
358 $new_post_id = wp_insert_post( $new_post );
359
360 set_post_format( $new_post_id, get_post_format( $post ) );
361 update_post_meta( $new_post_id, 'reblog', $post->guid );
362 update_post_meta( $new_post_id, 'reblog_of', $post->ID );
363 update_post_meta( $post->ID, 'reblogged', $new_post_id );
364 update_post_meta( $post->ID, 'reblogged_by', get_current_user_id() );
365
366 return $new_post_id;
367 }
368
369 public static function unreblog( $ret, $post ) {
370 if ( ! $post ) {
371 return $ret;
372 }
373 $post = get_post( $post );
374 if ( ! $post ) {
375 return $ret;
376 }
377
378 $reblogged = get_post_meta( $post->ID, 'reblogged', true );
379 if ( ! $reblogged ) {
380 return $ret;
381 }
382
383 wp_trash_post( $reblogged );
384
385 return $reblogged;
386 }
387
388 /**
389 * The Ajax function to be called upon posting from /friends
390 */
391 public function ajax_frontend_publish_post() {
392 if ( ! wp_verify_nonce( $_POST['_wpnonce'], 'friends_publish' ) ) {
393 return false;
394 }
395 $p = array(
396 'post_type' => 'post',
397 'post_title' => isset( $_POST['title'] ) ? $_POST['title'] : '',
398 'post_content' => isset( $_POST['content'] ) ? $_POST['content'] : '',
399 'post_status' => isset( $_POST['status'] ) ? $_POST['status'] : '',
400 'post_format' => isset( $_POST['format'] ) ? $_POST['format'] : '',
401 );
402
403 if ( empty( $p['post_status'] ) ) {
404 $p['post_status'] = 'publish';
405 }
406 $result = 'empty';
407
408 if ( ! empty( $_POST['in_reply_to'] ) ) {
409 $p['post_meta_input'] = array(
410 'activitypub_in_reply_to' => $_POST['in_reply_to'],
411 );
412 }
413
414 if ( ! empty( $p['post_content'] ) || ! empty( $p['post_title'] ) ) {
415 $post_id = wp_insert_post( $p );
416 if ( ! empty( $p['post_format'] ) ) {
417 set_post_format( $post_id, $p['post_format'] );
418 }
419 $result = is_wp_error( $post_id ) ? 'error' : 'success';
420 }
421 if ( ! empty( $_SERVER['HTTP_X_REQUESTED_WITH'] ) && strtolower( $_SERVER['HTTP_X_REQUESTED_WITH'] ) === 'xmlhttprequest' ) {
422 echo esc_html( $result );
423 exit;
424 } else {
425 wp_safe_redirect( remove_query_arg( 'in_reply_to', add_query_arg( 'result', $result, $_SERVER['HTTP_REFERER'] ) ) );
426 exit;
427 }
428 }
429
430 /**
431 * The Ajax function to change the post format from the Frontend.
432 */
433 public function ajax_change_post_format() {
434 $post_id = isset( $_POST['id'] ) ? (int) $_POST['id'] : 0;
435 $post_format = isset( $_POST['format'] ) ? $_POST['format'] : 'standard';
436
437 check_ajax_referer( "friends-change-post-format_$post_id" );
438
439 if ( ! current_user_can( Friends::REQUIRED_ROLE, $post_id ) ) {
440 wp_send_json_error();
441 exit;
442 }
443
444 $post_formats = get_post_format_strings();
445 if ( ! isset( $post_formats[ $post_format ] ) ) {
446 wp_send_json_error();
447 exit;
448 }
449
450 if ( ! set_post_format( $post_id, $post_format ) ) {
451 wp_send_json_error();
452 exit;
453 }
454
455 wp_send_json_success();
456 }
457
458 /**
459 * Calculates an estimating read time.
460 *
461 * @param string $original_text The original text.
462 *
463 * @return float The read time in seconds.
464 */
465 private static function calculate_read_time( $original_text ) {
466 // from wp_trim_words().
467 $text = wp_strip_all_tags( $original_text );
468
469 /*
470 * translators: If your word count is based on single characters (e.g. East Asian characters),
471 * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'.
472 * Do not translate into your own language.
473 */
474 if ( strpos( _x( 'words', 'Word count type. Do not translate!' ), 'characters' ) === 0 && preg_match( '/^utf\-?8$/i', get_option( 'blog_charset' ) ) ) { // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
475 $text = trim( preg_replace( "/[\n\r\t ]+/", ' ', $text ), ' ' );
476 preg_match_all( '/./u', $text, $words_array );
477 $words_array = array_shift( $words_array );
478 $words_per_minute = 500;
479 } else {
480 $words_array = preg_split( "/[\n\r\t ]+/", $text, -1, PREG_SPLIT_NO_EMPTY );
481 $words_per_minute = 200;
482 }
483
484 $additional_time = 0;
485 $figures = substr_count( strtolower( $original_text ), '<figure' );
486 for ( $i = 0; $i < $figures; $i++ ) {
487 if ( $i < 10 ) {
488 $additional_time += 12 - $i;
489 } else {
490 $additional_time += 3;
491 }
492 }
493
494 return count( $words_array ) / $words_per_minute * 60 + $additional_time;
495 }
496
497 /**
498 * Handles the post loop on the Friends page.
499 */
500 public static function have_posts() {
501 $friends = Friends::get_instance();
502 while ( have_posts() ) {
503 global $post;
504 the_post();
505 $args = array(
506 'friends' => $friends,
507 'avatar' => get_post_meta( get_the_ID(), 'gravatar', true ),
508 );
509
510 $args['friend_user'] = User::get_post_author( $post );
511
512 $read_time = self::calculate_read_time( get_the_content() );
513 if ( $read_time >= 60 ) {
514 $mins = ceil( $read_time / MINUTE_IN_SECONDS );
515 /* translators: Time difference between two dates, in minutes (min=minute). %s: Number of minutes. */
516 $args['read_time'] = sprintf( _n( '%s min', '%s mins', $mins ), $mins ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
517 } elseif ( $read_time > 20 ) {
518 /* translators: Time difference between two dates, in minutes (min=minute). %s: Number of minutes. */
519 $args['read_time'] = _x( '< 1 min', 'reading time', 'friends' );
520 }
521
522 Friends::template_loader()->get_template_part(
523 'frontend/parts/content',
524 get_post_format(),
525 $args
526 );
527 }
528 }
529
530 /**
531 * The Ajax function to load more posts for infinite scrolling.
532 */
533 public function ajax_load_next_page() {
534 $query_vars = wp_unslash( $_POST['query_vars'] );
535 if ( sha1( wp_salt( 'nonce' ) . $query_vars ) !== $_POST['qv_sign'] ) {
536 wp_send_json_error();
537 exit;
538 }
539 $query_vars = unserialize( $query_vars );
540 $query_vars['paged'] = intval( $_POST['page'] ) + 1;
541
542 query_posts( $query_vars );
543 ob_start();
544 if ( have_posts() ) {
545 self::have_posts();
546 } else {
547 esc_html_e( 'No further posts of your friends could were found.', 'friends' );
548 }
549
550 $posts = ob_get_contents();
551 ob_end_clean();
552
553 wp_send_json_success( $posts );
554 }
555
556 /**
557 * Get metadata for in_reply_to_preview.
558 *
559 * @param string $url The url.
560 *
561 * @return array|WP_Error The in reply to metadata.
562 */
563 function get_in_reply_to_metadata( $url ) {
564 $meta = apply_filters( 'friends_get_activitypub_metadata', array(), $url );
565 if ( is_wp_error( $meta ) ) {
566 return $meta;
567 }
568
569 if ( ! $meta || ! isset( $meta['attributedTo'] ) ) {
570 return new \WP_Error( 'no-activitypub', 'No ActivityPub metadata found.' );
571 }
572
573 $html = 'URL: ' . make_clickable( $meta['id'] );
574 $html .= '<blockquote>' . force_balance_tags( wp_kses_post( $meta['content'] ) ) . '</blockquote>';
575
576 $webfinger = apply_filters( 'friends_get_activitypub_metadata', array(), $meta['attributedTo'] );
577 $mention = '';
578 if ( $webfinger && ! is_wp_error( $webfinger ) ) {
579 $mention = '@' . $webfinger['preferredUsername'] . '@' . parse_url( $url, PHP_URL_HOST );
580 }
581
582 return array(
583 'url' => $url,
584 'html' => $html,
585 'author' => $meta['attributedTo'],
586 'mention' => $mention,
587 );
588
589 }
590
591 /**
592 * The Ajax function to fill the in-reply-to-preview.
593 */
594 function ajax_in_reply_to_preview() {
595 $url = wp_unslash( $_POST['url'] );
596
597 if ( ! wp_parse_url( $url ) ) {
598 wp_send_json_error();
599 exit;
600 }
601
602 $meta = $this->get_in_reply_to_metadata( $_POST['url'] );
603
604 if ( is_wp_error( $meta ) ) {
605 wp_send_json_error( $meta->get_error_message() );
606 exit;
607 }
608 wp_send_json_success( $meta );
609 }
610 /**
611 * The Ajax function to autocomplete search.
612 */
613 function ajax_autocomplete() {
614 $q = wp_unslash( $_POST['q'] );
615 $users = User_Query::search( '*' . $q . '*' );
616 $results = array();
617 foreach ( $users->get_results() as $friend ) {
618 $result = '<li class="menu-item">';
619 $result .= '<a href="' . esc_url( $friend->get_local_friends_page_url() ) . '" class="has-icon-left">';
620 $result .= str_ireplace( $q, '<mark>' . $q . '</mark>', $friend->display_name );
621 $result .= ' <small>';
622 $result .= str_ireplace( $q, '<mark>' . $q . '</mark>', $friend->user_login );
623 $result .= '</small></a></li>';
624 $results[] = $result;
625 }
626
627 wp_send_json_success( implode( PHP_EOL, $results ) );
628
629 }
630
631 /**
632 * The Ajax function to load comments.
633 */
634 function ajax_load_comments() {
635 if ( ! isset( $_POST['post_id'] ) || ! intval( $_POST['post_id'] ) ) {
636 wp_send_json_error();
637 exit;
638 }
639
640 $post_id = intval( $_POST['post_id'] );
641 check_ajax_referer( "comments-$post_id" );
642
643 $comments = get_comments(
644 array(
645 'post_id' => $post_id,
646 )
647 );
648
649 $author_id = get_post_field( 'post_author', $post_id );
650 $friend_user = new User( $author_id );
651
652 $comments_url = get_post_meta( $post_id, Feed::COMMENTS_FEED_META, true );
653 if ( ! $comments_url && empty( $comments ) ) {
654 wp_send_json_error( __( 'No comments feed available.', 'friends' ) );
655 exit;
656 }
657
658 if ( $friend_user->is_friend_url( $comments_url ) && friends::has_required_privileges() || wp_doing_cron() ) {
659 $comments_url = apply_filters( 'friends_friend_private_feed_url', $comments_url, $friend_user );
660 $comments_url = $this->friends->access_control->append_auth( $comments_url, $friend_user, 300 );
661 }
662
663 $feed_comments = $this->friends->feed->preview( 'simplepie', $comments_url );
664 if ( empty( $comments ) && ( is_wp_error( $feed_comments ) || ! is_array( $feed_comments ) ) ) {
665 wp_send_json_error( '<small>' . __( 'Unfortunately, comments were not available via RSS.', 'friends' ) . '</small>' );
666 exit;
667 } elseif ( is_wp_error( $feed_comments ) ) {
668 $feed_comments = array();
669 }
670
671 $template_loader = Friends::template_loader();
672 ob_start();
673 ?>
674 <h5><?php esc_html_e( 'Comments' ); /* phpcs:ignore WordPress.WP.I18n.MissingArgDomain */ ?></h5>
675 <?php
676 foreach ( $comments as $comment ) {
677 $template_loader->get_template_part(
678 'frontend/parts/comment',
679 null,
680 array(
681 'author' => $comment->comment_author,
682 'date' => $comment->comment_date,
683 'permalink' => $comment->guid . '#comment-' . $comment->comment_ID,
684 'post_content' => $comment->comment_content,
685 )
686 );
687 }
688 foreach ( $feed_comments as $comment ) {
689 $template_loader->get_template_part(
690 'frontend/parts/comment',
691 null,
692 array(
693 'author' => $comment->author,
694 'date' => $comment->date,
695 'permalink' => $comment->permalink,
696 'post_content' => $comment->post_content,
697 )
698 );
699
700 }
701 ?>
702 <p>
703 <a href="<?php echo esc_url( get_comments_link( $post_id ) ); ?>"><?php esc_html_e( 'Leave a Comment' ); /* phpcs:ignore WordPress.WP.I18n.MissingArgDomain */ ?></a>
704 </p>
705 <?php
706 $content = ob_get_contents();
707 ob_end_clean();
708
709 wp_send_json_success( $content );
710 }
711
712 public function ajax_star_friend_user() {
713 if ( ! isset( $_POST['friend_id'] ) || ! $_POST['friend_id'] ) {
714 wp_send_json_error();
715 exit;
716 }
717
718 $friend_id = wp_unslash( $_POST['friend_id'] );
719 check_ajax_referer( "star-$friend_id" );
720
721 $friend_user = User::get_by_username( $friend_id );
722
723 $starred = boolval( $_POST['starred'] );
724 $friend_user->set_starred( $starred );
725
726 wp_send_json_success(
727 array(
728 'starred' => $friend_user->get_starred(),
729 )
730 );
731 }
732
733 /**
734 * Ensure that untrashed friends posts go back to published.
735 *
736 * @param string $new_status The new status of the post being restored.
737 * @param int $post_id The ID of the post being restored.
738 * @param string $previous_status The status of the post at the point where it was trashed.
739 */
740 public function untrash_post_status( $new_status, $post_id, $previous_status ) {
741 if ( in_array( get_post_type( $post_id ), apply_filters( 'friends_frontend_post_types', array() ), true ) ) {
742 return $new_status;
743 }
744 return 'publish';
745 }
746
747 /**
748 * Load the template for /friends
749 *
750 * @param string $template The original template intended to load.
751 * @return string The new template to be loaded.
752 */
753 public function template_override( $template ) {
754 if ( ! Friends::on_frontend() ) {
755 return $template;
756 }
757
758 if ( isset( $_GET['refresh'] ) ) {
759 add_filter( 'notify_about_new_friend_post', '__return_false', 999 );
760 add_filter(
761 'wp_feed_options',
762 function( $feed ) {
763 $feed->enable_cache( false );
764 }
765 );
766 $this->friends->feed->retrieve_friend_posts( true );
767 }
768
769 global $args;
770 $args = array(
771 'friends' => $this->friends,
772 'friend_user' => $this->author,
773 'frontend_default_view' => get_option( 'friends_frontend_default_view', 'expanded' ),
774 );
775
776 if ( isset( $_GET['in_reply_to'] ) && wp_parse_url( $_GET['in_reply_to'] ) ) {
777 $args['in_reply_to'] = $args['friends']->frontend->get_in_reply_to_metadata( $_GET['in_reply_to'] );
778 }
779
780 return Friends::template_loader()->get_template_part( 'frontend/index', null, $args, false );
781 }
782
783 /**
784 * Modify the Friends page title depending on context, for example add an author name or post format.
785 *
786 * @param string $title The original title.
787 *
788 * @return string The modified title.
789 */
790 function header_widget_title( $title ) {
791 $title = '<a href="' . esc_url( home_url( '/friends/' ) ) . '">' . esc_html( $title ) . '</a>';
792 if ( $this->author ) {
793 $title .= ' &raquo; ' . '<a href="' . esc_url( $this->author->get_local_friends_page_url() ) . '">' . esc_html( $this->author->display_name ) . '</a>';
794 }
795 if ( $this->post_format ) {
796 $post_formats = get_post_format_strings();
797 $title .= ' &raquo; ' . $post_formats[ $this->post_format ];
798 }
799 return $title;
800 }
801
802 /**
803 * Output a link, potentially augmented with authication information.
804 *
805 * @param string $url The url.
806 * @param string $text The link text.
807 * @param array $html_attributes HTML attributes.
808 * @param User $friend_user The friend user.
809 */
810 function link( $url, $text, array $html_attributes = array(), User $friend_user = null ) {
811 echo wp_kses(
812 self::get_link( $url, $text, $html_attributes, $friend_user ),
813 array(
814 'a' => array(
815 'href' => array(),
816 'title' => array(),
817 'target' => array(),
818 'rel' => array(),
819 'class' => array(),
820 'style' => array(),
821 'data-nonce' => array(),
822 'data-cnonce' => array(),
823 'data-token' => array(),
824 'data-friend' => array(),
825 'data-id' => array(),
826 'data-url' => array(),
827 ),
828 'span' => array( 'class' => array() ),
829 )
830 );
831 }
832
833 /**
834 * Get a link, potentially augmented with authication information.
835 *
836 * @param string $url The url.
837 * @param string $text The link text.
838 * @param array $html_attributes HTML attributes.
839 * @param User $friend_user The friend user.
840 *
841 * @return string The link.
842 */
843 public static function get_link( $url, $text, array $html_attributes = array(), User $friend_user = null ) {
844 if ( is_null( $friend_user ) ) {
845 $friend_user = new User( get_the_author_meta( 'ID' ) );
846 }
847
848 if ( $friend_user->is_friend_url( $url ) && $friend_user->is_valid_friend() ) {
849 $html_attributes['target'] = '_blank';
850 $html_attributes['rel'] = 'noopener noreferrer';
851 if ( ! isset( $html_attributes['class'] ) ) {
852 $html_attributes['class'] = '';
853 }
854 $html_attributes['class'] = trim( $html_attributes['class'] . ' friends-auth-link' );
855 if ( ! isset( $html_attributes['dashicon_back'] ) ) {
856 $html_attributes['dashicon_back'] = 'admin-users';
857 }
858 $html_attributes['data-token'] = $friend_user->get_friend_auth();
859 $html_attributes['data-nonce'] = wp_create_nonce( 'auth-link-' . $url );
860 $html_attributes['data-friend'] = $friend_user->user_login;
861 }
862
863 $link = '<a href="' . esc_url( $url ) . '"';
864 foreach ( $html_attributes as $name => $value ) {
865 if ( ! in_array( $name, array( 'title', 'target', 'rel', 'class', 'style', 'data-nonce', 'data-cnonce', 'data-token', 'data-friend', 'data-id', 'data-url' ) ) ) {
866 continue;
867 }
868 $link .= ' ' . $name . '="' . esc_attr( $value ) . '"';
869 }
870 $link .= '>';
871 if ( isset( $html_attributes['dashicon_front'] ) ) {
872 $link .= '<span class="dashicons dashicons-' . esc_attr( $html_attributes['dashicon_front'] ) . '"></span>';
873 }
874 $link .= esc_html( $text );
875 if ( isset( $html_attributes['dashicon_back'] ) ) {
876 $link .= '<span class="dashicons dashicons-' . esc_attr( $html_attributes['dashicon_back'] ) . '"></span>';
877 }
878 $link .= '</a>';
879
880 return $link;
881 }
882
883 /**
884 * Don't show the edit link for friend posts.
885 *
886 * @param string $link The edit link.
887 * @return string|bool The edit link or false.
888 */
889 public function friend_post_edit_link( $link ) {
890 global $post;
891
892 if ( $post && in_array( $post->post_type, apply_filters( 'friends_frontend_post_types', array() ), true ) ) {
893 if ( Friends::on_frontend() ) {
894 $new_link = false;
895 } else {
896 $new_link = get_the_guid( $post );
897 }
898 /**
899 * Allow overriding the link for editing friend posts.
900 *
901 * By default on the Frontend, a post cannot be edited because $new_link is false.
902 *
903 * Example:
904 *
905 * ```php
906 * add_filter( 'friend_post_edit_link', function( $link, $original_link ) {
907 * if ( ! $link ) {
908 * $link = $original_link; // always allow editing.
909 * }
910 * return $link;
911 * }, 10, 2 );
912 * ```
913 */
914 return apply_filters( 'friend_post_edit_link', $new_link, $link );
915 }
916 return $link;
917 }
918
919 /**
920 * Link friend posts to the remote site.
921 *
922 * @param string $post_link The post's permalink.
923 * @param \WP_Post $post The post in question.
924 * @reeturn string The overriden post link.
925 */
926 public function friend_post_link( $post_link, \WP_Post $post ) {
927 if ( $post && in_array( $post->post_type, apply_filters( 'friends_frontend_post_types', array() ), true ) ) {
928 return get_the_guid( $post );
929 }
930 return $post_link;
931 }
932
933 /**
934 * Potentially override the post author name with metadata.
935 *
936 * @param string $overridden_author_name The already overridden author name.
937 * @param string $author_name The author name.
938 * @param int $post_id The post id.
939 *
940 * @return string The modified author name.
941 */
942 public function override_author_name( $overridden_author_name, $author_name, $post_id ) {
943 if ( $overridden_author_name && $overridden_author_name !== $author_name ) {
944 return $overridden_author_name;
945 }
946 $override_author_name = get_post_meta( $post_id, 'author', true );
947 if ( $override_author_name ) {
948 return $override_author_name;
949 }
950 return $author_name;
951 }
952
953 /**
954 * Render the Friends OPML
955 *
956 * @param bool $only_public Only public feed URLs.
957 */
958 protected function render_opml( $only_public = false ) {
959 $user = wp_get_current_user();
960
961 // translators: %s is a name.
962 $title = sprintf( __( "%s' Subscriptions", 'friends' ), $user->display_name );
963 $filename = 'friends-';
964 if ( ! $only_public ) {
965 $title = __( 'My Friends', 'friends' );
966 $filename .= 'private-';
967 }
968 $filename .= $user->user_login . '.opml';
969
970 $feeds = array();
971 $users = array();
972
973 $friend_users = new User_Query( array( 'role__in' => array( 'friend', 'acquaintance', 'friend_request', 'subscription' ) ) );
974 foreach ( $friend_users->get_results() as $friend_user ) {
975 $role = $friend_user->get_role_name( true, 9 );
976 if ( $only_public ) {
977 $role = 'Feeds';
978 }
979 if ( ! isset( $users[ $role ] ) ) {
980 $users[ $role ] = array();
981 }
982
983 $users[ $role ][] = $friend_user;
984 }
985 ksort( $users );
986 foreach ( $users as $role => $friend_users ) {
987 foreach ( $friend_users as $friend_user ) {
988 $user_feeds = $friend_user->get_active_feeds();
989
990 $need_local_feed = false;
991
992 foreach ( $user_feeds as $feed ) {
993 switch ( $feed->get_mime_type() ) {
994 case 'application/atom+xml':
995 case 'application/atomxml':
996 case 'application/rss+xml':
997 case 'application/rssxml':
998 break;
999 default:
1000 $need_local_feed = true;
1001 break 2;
1002 }
1003 }
1004
1005 if ( $need_local_feed && ! $only_public ) {
1006 $user_feeds = array_slice( $user_feeds, 0, 1 );
1007 }
1008
1009 $user = array(
1010 'friend_user' => $friend_user,
1011 'feeds' => array(),
1012 );
1013 $html_url = $friend_user->user_url;
1014
1015 foreach ( $user_feeds as $feed ) {
1016 $type = 'rss';
1017 $feed_title = $friend_user->display_name;
1018
1019 if ( ! $only_public ) {
1020 $html_url = $feed->get_local_html_url();
1021 }
1022
1023 if ( $need_local_feed ) {
1024 if ( $only_public ) {
1025 // Cannot create a public URL.
1026 continue;
1027 }
1028 $xml_url = $feed->get_local_url() . '?auth=' . $_GET['auth'];
1029 } else {
1030 if ( $only_public ) {
1031 $xml_url = $feed->get_url();
1032 } else {
1033 $xml_url = $feed->get_private_url( YEAR_IN_SECONDS );
1034 }
1035 if ( 'application/atom+xml' === $feed->get_mime_type() ) {
1036 $type = 'atom';
1037 }
1038 }
1039 $user['feeds'][] = array(
1040 'xml_url' => $xml_url,
1041 'html_url' => $html_url,
1042 'title' => $feed_title,
1043 'type' => $type,
1044 );
1045 }
1046
1047 if ( ! empty( $user['feeds'] ) ) {
1048 if ( ! isset( $feeds[ $role ] ) ) {
1049 $feeds[ $role ] = array();
1050 }
1051 $feeds[ $role ][] = $user;
1052 }
1053 }
1054 }
1055 Friends::template_loader()->get_template_part(
1056 'admin/opml',
1057 null,
1058 array(
1059 'title' => $title,
1060 'feeds' => $feeds,
1061 'filename' => $filename,
1062 )
1063 );
1064 exit;
1065 }
1066
1067 private function has_required_priviledges() {
1068 if ( Friends::is_main_user() ) {
1069 return true;
1070 }
1071
1072 if ( is_multisite() ) {
1073 if ( is_user_member_of_blog( get_current_user_id(), get_current_blog_id() ) ) {
1074 return true;
1075 }
1076
1077 if ( is_super_admin( get_current_user_id() ) ) {
1078 // Super admins would count as administrators.
1079 return false;
1080 }
1081 }
1082
1083 if ( current_user_can( 'manage_options' ) ) {
1084 return true;
1085 }
1086
1087 return false;
1088
1089 }
1090
1091 /**
1092 * Modify the main query for the /friends page
1093 *
1094 * @param \WP_Query $query The main query.
1095 * @return \WP_Query The modified main query.
1096 */
1097 public function friend_posts_query( $query ) {
1098 global $wp_query, $wp, $authordata;
1099 if ( $wp_query !== $query || $query->is_admin() || $query->is_home() ) {
1100 return $query;
1101 }
1102
1103 $pagename = '';
1104 if ( isset( $wp_query->query['pagename'] ) ) {
1105 $pagename = $wp_query->query['pagename'];
1106 } elseif ( isset( $wp_query->query['name'] ) ) {
1107 $pagename = $wp_query->query['name'];
1108 }
1109
1110 $pagename_parts = explode( '/', trim( $pagename, '/' ) );
1111 $is_friends = array_shift( $pagename_parts );
1112 if ( 'friends' !== $is_friends ) {
1113 return $query;
1114 }
1115
1116 // Not available for the general public or friends.
1117 $viewable = $this->has_required_priviledges();
1118 if ( $query->is_feed() ) {
1119 // Feeds can be viewed through extra authentication.
1120 if ( $this->friends->access_control->private_rss_is_authenticated() ) {
1121 $viewable = true;
1122 } elseif ( isset( $wp_query->query['pagename'] ) ) {
1123 $pagename_parts = explode( '/', trim( $wp_query->query['pagename'], '/' ) );
1124 if ( apply_filters( 'friends_friend_feed_viewable', false, $pagename_parts[1] ) ) {
1125 $viewable = true;
1126 }
1127 }
1128 }
1129 $page_id = get_query_var( 'page' );
1130
1131 if ( isset( $_GET['share'] ) ) {
1132 $share_hash = hash( 'crc32b', apply_filters( 'friends_share_salt', wp_salt( 'nonce' ), $page_id ) . $page_id );
1133 if ( $_GET['share'] === $share_hash ) {
1134 $viewable = true;
1135 }
1136 }
1137
1138 if ( ! $viewable || ! Friends::on_frontend() ) {
1139 if ( $query->is_feed() ) {
1140 status_header( 404 );
1141 $query->set_404();
1142 } elseif ( ! Friends::on_frontend() ) {
1143 if ( count( $pagename_parts ) > 0 ) {
1144 wp_safe_redirect( home_url( '/friends/' ) );
1145 exit;
1146 }
1147 } elseif ( ! Friends::is_main_user() ) {
1148 wp_die( __( 'You are not allowed to view this page.', 'friends' ) );
1149 }
1150
1151 return $query;
1152 }
1153
1154 // Don't let the Post Kinds plugin interfere with this query.
1155 remove_action( 'pre_get_posts', array( 'Kind_Taxonomy', 'kind_firehose_query' ), 99 );
1156
1157 switch_to_locale( get_user_locale() );
1158
1159 $tax_query = array();
1160 $post_format = null;
1161
1162 while ( $pagename_parts ) {
1163 $pagename_part = $pagename_parts[0];
1164 $current_part = array_shift( $pagename_parts );
1165 if ( 'reaction' === substr( $current_part, 0, 8 ) && strlen( $current_part ) > 8 ) {
1166 $reaction = Reactions::validate_emoji( substr( $current_part, 8 ) );
1167 if ( ! $reaction ) {
1168 continue;
1169 }
1170 $this->reaction = $reaction;
1171 if ( ! empty( $tax_query ) ) {
1172 $tax_query['relation'] = 'AND';
1173 }
1174
1175 $tax_query[] = array(
1176 'taxonomy' => 'friend-reaction-' . get_current_user_id(),
1177 'field' => 'slug',
1178 'terms' => array( substr( $current_part, 8 ) ),
1179 );
1180 continue;
1181 }
1182
1183 switch ( $current_part ) {
1184 case 'opml':
1185 return $this->render_opml( isset( $_REQUEST['public'] ) );
1186
1187 case 'type':
1188 $post_format = array_shift( $pagename_parts );
1189 $tax_query = $this->friends->wp_query_get_post_format_tax_query( $tax_query, explode( ',', $post_format ) );
1190 if ( $tax_query ) {
1191 $this->post_format = $post_format;
1192 }
1193 break;
1194
1195 default: // Maybe an author.
1196 $author = User::get_by_username( $current_part );
1197 if ( false === $author || is_wp_error( $author ) ) {
1198 if ( $query->is_feed() ) {
1199 status_header( 404 );
1200 $query->set_404();
1201 return $query;
1202 }
1203 wp_safe_redirect( home_url( '/friends/' ) );
1204 exit;
1205 }
1206
1207 $this->author = $author;
1208 break;
1209 }
1210 }
1211
1212 $this->is_friends_page = true;
1213 $query->is_friends_page = true;
1214 $query->is_singular = false;
1215 $query->is_single = false;
1216 $query->queried_object = null;
1217 $query->queried_object_id = null;
1218 $post_types = apply_filters( 'friends_frontend_post_types', array() );
1219
1220 if ( 'status' === $post_format ) {
1221 // Show your own posts on the status feed.
1222 $post_types[] = 'post';
1223 }
1224
1225 $query->set( 'post_type', $post_types );
1226 $query->set( 'tax_query', $tax_query );
1227
1228 if ( ( isset( $_GET['share'] ) && $_GET['share'] === $share_hash ) || friends::has_required_privileges() ) {
1229 $post_status = array( 'publish', 'private' );
1230 if ( isset( $_GET['show-hidden'] ) ) {
1231 $post_status[] = 'trash';
1232 }
1233 $query->set( 'post_status', $post_status );
1234 }
1235 $query->is_page = false;
1236 $query->is_comments_feed = false;
1237 $query->set( 'pagename', null );
1238 if ( 'collapsed' === get_option( 'friends_frontend_default_view', 'expanded' ) && get_option( 'posts_per_page' ) < 20 ) {
1239 if ( 'status' === $post_format ) {
1240 $query->set( 'posts_per_page', 30 );
1241 } else {
1242 $query->set( 'posts_per_page', 20 );
1243 }
1244 }
1245
1246 if ( $page_id ) {
1247 $query->set( 'page_id', $page_id );
1248 if ( ! $this->author ) {
1249 $post = get_post( $page_id );
1250 $author = User::get_post_author( $post );
1251 if ( false !== $author ) {
1252 $this->author = $author;
1253 }
1254 }
1255 $query->is_single = true;
1256 $query->is_singular = true;
1257 $wp->set_query_var( 'page', null );
1258 $wp->set_query_var( 'page_id', $page_id );
1259 }
1260
1261 if ( $this->author ) {
1262 if ( $this->author instanceof User ) {
1263 $authordata = $this->author;
1264 $this->author->modify_query_by_author( $query );
1265 if ( ! $page_id ) {
1266 $query->is_author = true;
1267 }
1268 } else {
1269 $this->author = null;
1270 }
1271 }
1272
1273 if ( ! $query->is_singular && ! $query->is_author ) {
1274 // This is the main friends page.
1275 $hide_from_friends_page = get_user_option( 'friends_hide_from_friends_page' );
1276 if ( $hide_from_friends_page ) {
1277 $query->set( 'author__not_in', $hide_from_friends_page );
1278 }
1279 }
1280
1281 return $query;
1282 }
1283 }
1284