PluginProbe
Friends / 4.2.1
Friends v4.2.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-blocks.php

class-blocks.php in Friends 4.2.1, at includes/class-blocks.php

1,782 lines 61.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Friends Blocks
4 *
5 * This contains the functions for blocks.
6 *
7 * @package Friends
8 */
9
10 namespace Friends;
11
12 /**
13 * This is the class for the Friends Plugin blocks.
14 *
15 * @since 0.8
16 *
17 * @package Friends
18 * @author Alex Kirk
19 */
20 class Blocks {
21
22 /**
23 * Constructor
24 */
25 public function __construct() {
26 $this->register_hooks();
27 }
28
29 /**
30 * Register the WordPress hooks
31 */
32 private function register_hooks() {
33 if ( ! function_exists( 'register_block_type' ) ) {
34 return;
35 }
36 if ( function_exists( 'classicpress_version' ) ) {
37 return;
38 }
39 add_action( 'admin_enqueue_scripts', array( $this, 'language_data' ) );
40 add_filter( 'render_block', array( $this, 'render_friends_block_visibility' ), 10, 2 );
41 add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_sidebar_blocks' ) );
42 add_action( 'init', array( $this, 'register_blocks' ) );
43 }
44
45 /**
46 * Safely get block wrapper attributes, returning a fallback when not in a block context.
47 *
48 * @param array $extra_attributes Extra attributes to add.
49 * @return string The wrapper attributes string.
50 */
51 private function get_wrapper_attributes( $extra_attributes = array() ) {
52 if ( function_exists( 'get_block_wrapper_attributes' ) && \WP_Block_Supports::$block_to_render ) {
53 return get_block_wrapper_attributes( $extra_attributes );
54 }
55 $attrs = '';
56 foreach ( $extra_attributes as $key => $value ) {
57 $attrs .= ' ' . $key . '="' . esc_attr( $value ) . '"';
58 }
59 return $attrs;
60 }
61
62 /**
63 * Register our blocks.
64 */
65 public function register_blocks() {
66 if ( ! function_exists( 'register_block_type_from_metadata' ) ) {
67 // Blocks is not active.
68 return;
69 }
70 register_block_type_from_metadata(
71 FRIENDS_PLUGIN_DIR . '/blocks/friends-list',
72 array(
73 'render_callback' => array( $this, 'render_friends_list_block' ),
74 )
75 );
76
77 register_block_type(
78 'friends/subscriptions-query',
79 array(
80 'render_callback' => array( $this, 'render_subscriptions_query_block' ),
81 )
82 );
83
84 register_block_type(
85 'friends/subscription',
86 array(
87 'uses_context' => array( 'friends/subscription' ),
88 'render_callback' => array( $this, 'render_subscription_block' ),
89 )
90 );
91
92 register_block_type(
93 'friends/followers',
94 array(
95 'render_callback' => array( $this, 'render_followers_block' ),
96 )
97 );
98
99 register_block_type(
100 'friends/add-friend-form',
101 array(
102 'render_callback' => array( $this, 'render_add_friend_form_block' ),
103 )
104 );
105
106 $list_supports = array(
107 'color' => array(
108 'background' => true,
109 'text' => true,
110 'link' => true,
111 ),
112 'typography' => array(
113 'fontSize' => true,
114 'lineHeight' => true,
115 ),
116 'spacing' => array(
117 'padding' => true,
118 'margin' => true,
119 ),
120 );
121
122 $chip_supports = array(
123 'color' => array(
124 'background' => true,
125 'text' => true,
126 'link' => true,
127 ),
128 'typography' => array(
129 'fontSize' => true,
130 ),
131 'spacing' => array(
132 'padding' => true,
133 'margin' => true,
134 ),
135 );
136
137 register_block_type(
138 'friends/stats',
139 array(
140 'render_callback' => array( $this, 'render_stats_block' ),
141 'supports' => $list_supports,
142 )
143 );
144
145 register_block_type(
146 'friends/refresh',
147 array(
148 'render_callback' => array( $this, 'render_refresh_block' ),
149 'supports' => $list_supports,
150 )
151 );
152
153 register_block_type(
154 'friends/post-formats',
155 array(
156 'render_callback' => array( $this, 'render_post_formats_block' ),
157 'supports' => $list_supports,
158 )
159 );
160
161 register_block_type(
162 'friends/add-subscription',
163 array(
164 'render_callback' => array( $this, 'render_add_subscription_block' ),
165 'supports' => $list_supports,
166 )
167 );
168
169 register_block_type(
170 'friends/search',
171 array(
172 'render_callback' => array( $this, 'render_search_block' ),
173 )
174 );
175
176 register_block_type(
177 'friends/feed-title',
178 array(
179 'render_callback' => array( $this, 'render_feed_title_block' ),
180 'supports' => array(
181 'color' => array(
182 'background' => true,
183 'text' => true,
184 'link' => true,
185 ),
186 'typography' => array(
187 'fontSize' => true,
188 'lineHeight' => true,
189 ),
190 ),
191 )
192 );
193
194 register_block_type(
195 'friends/feed-chips',
196 array(
197 'render_callback' => array( $this, 'render_feed_chips_block' ),
198 'supports' => $chip_supports,
199 )
200 );
201
202 register_block_type(
203 'friends/welcome',
204 array(
205 'attributes' => array(
206 'forceWelcome' => array(
207 'type' => 'boolean',
208 'default' => false,
209 ),
210 ),
211 'render_callback' => array( $this, 'render_welcome_block' ),
212 'supports' => $list_supports,
213 )
214 );
215
216 register_block_type(
217 'friends/post-content',
218 array(
219 'render_callback' => array( $this, 'render_post_content_block' ),
220 )
221 );
222
223 register_block_type(
224 'friends/post-permalink',
225 array(
226 'render_callback' => array( $this, 'render_post_permalink_block' ),
227 'supports' => $list_supports,
228 )
229 );
230
231 register_block_type(
232 'friends/post-reblog',
233 array(
234 'render_callback' => array( $this, 'render_post_reblog_block' ),
235 )
236 );
237
238 register_block_type(
239 'friends/post-boost',
240 array(
241 'render_callback' => array( $this, 'render_post_boost_block' ),
242 )
243 );
244
245 register_block_type(
246 'friends/post-reactions',
247 array(
248 'render_callback' => array( $this, 'render_post_reactions_block' ),
249 )
250 );
251
252 register_block_type(
253 'friends/post-comments',
254 array(
255 'render_callback' => array( $this, 'render_post_comments_block' ),
256 )
257 );
258
259 register_block_type(
260 'friends/author-star',
261 array(
262 'render_callback' => array( $this, 'render_author_star_block' ),
263 )
264 );
265
266 register_block_type(
267 'friends/author-avatar',
268 array(
269 'render_callback' => array( $this, 'render_author_avatar_block' ),
270 )
271 );
272
273 register_block_type(
274 'friends/author-name',
275 array(
276 'render_callback' => array( $this, 'render_author_name_block' ),
277 'supports' => array(
278 'color' => array(
279 'background' => true,
280 'text' => true,
281 'link' => true,
282 ),
283 'typography' => array(
284 'fontSize' => true,
285 'lineHeight' => true,
286 ),
287 ),
288 )
289 );
290
291 register_block_type(
292 'friends/author-description',
293 array(
294 'render_callback' => array( $this, 'render_author_description_block' ),
295 'supports' => $list_supports,
296 )
297 );
298
299 register_block_type(
300 'friends/author-chips',
301 array(
302 'render_callback' => array( $this, 'render_author_chips_block' ),
303 'supports' => $chip_supports,
304 )
305 );
306 }
307
308 /**
309 * Render the friends/subscriptions-query block.
310 *
311 * @param array $attributes Block attributes.
312 * @param string $content Inner block content.
313 * @param WP_Block $block Block instance.
314 * @return string The rendered block HTML.
315 */
316 public function render_subscriptions_query_block( $attributes, $content, $block ) {
317 $subscriptions = User_Query::all_subscriptions()->get_results();
318
319 if ( empty( $subscriptions ) ) {
320 return '<p>' . esc_html__( "You're not following anyone yet.", 'friends' ) . '</p>';
321 }
322
323 $items = '';
324 foreach ( $subscriptions as $subscription ) {
325 $context = array_merge(
326 $block->context,
327 array(
328 'friends/subscription' => array(
329 'ID' => $subscription->ID,
330 'display_name' => $subscription->display_name,
331 'user_url' => $subscription->user_url,
332 'avatar_url' => $subscription->get_avatar_url(),
333 'page_url' => $subscription->get_local_friends_page_url(),
334 ),
335 )
336 );
337
338 $inner_content = '';
339 foreach ( $block->parsed_block['innerBlocks'] as $inner_block ) {
340 $inner_content .= ( new \WP_Block( $inner_block, $context ) )->render();
341 }
342 $items .= '<li>' . $inner_content . '</li>';
343 }
344
345 return '<ul class="wp-block-friends-subscriptions-query">' . $items . '</ul>';
346 }
347
348 /**
349 * Render the friends/add-friend-form block.
350 *
351 * @return string The rendered block HTML.
352 */
353 public function render_add_friend_form_block() {
354 ob_start();
355 Friends::template_loader()->get_template_part( 'frontend/add-friend-form' );
356 $form = ob_get_clean();
357
358 return '<div' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-add-friend-form' ) ) . '>' . $form . '</div>';
359 }
360
361 /**
362 * Render the friends/subscription block.
363 *
364 * @param array $attributes Block attributes.
365 * @param string $content Inner block content.
366 * @param WP_Block $block Block instance.
367 * @return string The rendered block HTML.
368 */
369 public function render_subscription_block( $attributes, $content, $block ) {
370 if ( empty( $block->context['friends/subscription'] ) ) {
371 return '';
372 }
373
374 $subscription = $block->context['friends/subscription'];
375
376 $out = '<div class="wp-block-friends-subscription">';
377 $out .= '<a href="' . esc_url( $subscription['page_url'] ) . '">';
378 if ( $subscription['avatar_url'] ) {
379 $out .= '<img src="' . esc_url( $subscription['avatar_url'] ) . '" alt="" class="avatar" width="40" height="40" />';
380 }
381 $out .= '<span>' . esc_html( $subscription['display_name'] ) . '</span>';
382 $out .= '</a>';
383 $out .= '</div>';
384
385 return $out;
386 }
387
388 /**
389 * Render the friends/followers block.
390 *
391 * @return string The rendered block HTML.
392 */
393 public function render_followers_block() {
394 global $friends_args;
395
396 if ( ! class_exists( '\ActivityPub\Collection\Followers' ) ) {
397 return '<section class="followers"><p>' . esc_html__( 'The follower list is currently dependent on the ActivityPub plugin.', 'friends' ) . '</p></section>';
398 }
399
400 $user_id = isset( $friends_args['user_id'] ) ? $friends_args['user_id'] : get_current_user_id();
401 $blog_followers = class_exists( '\ActivityPub\Collection\Actors' ) && \ActivityPub\Collection\Actors::BLOG_USER_ID === $user_id;
402
403 $filter = isset( $_GET['filter'] ) ? sanitize_key( $_GET['filter'] ) : 'all'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
404 if ( ! in_array( $filter, array( 'all', 'following', 'not-following' ), true ) ) {
405 $filter = 'all';
406 }
407 // Backwards compatibility with ?mutual.
408 if ( isset( $_GET['mutual'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
409 $filter = 'following';
410 }
411
412 $sort = isset( $_GET['sort'] ) ? sanitize_key( $_GET['sort'] ) : 'newest'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
413 if ( ! in_array( $sort, array( 'newest', 'oldest', 'name' ), true ) ) {
414 $sort = 'newest';
415 }
416
417 $search_term = isset( $_GET['q'] ) ? trim( wp_unslash( $_GET['q'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.MissingUnslash,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
418
419 $followers_list_page = 'users.php?page=activitypub-followers-list';
420 if ( $blog_followers ) {
421 $followers_list_page = 'options-general.php?page=activitypub&tab=followers';
422 }
423
424 $follower_data = \ActivityPub\Collection\Followers::query( $user_id );
425 $total = $follower_data['total'];
426
427 // Apply search filter first so downstream work operates on the smaller set.
428 if ( '' !== $search_term ) {
429 $search_term_lc = function_exists( 'mb_strtolower' ) ? mb_strtolower( $search_term ) : strtolower( $search_term );
430 $follower_data['followers'] = array_values(
431 array_filter(
432 $follower_data['followers'],
433 function ( $f ) use ( $search_term_lc ) {
434 $haystack = '';
435 if ( isset( $f->post_title ) ) {
436 $haystack .= $f->post_title . ' ';
437 }
438 if ( isset( $f->guid ) ) {
439 $haystack .= $f->guid . ' ';
440 }
441 if ( isset( $f->post_content ) ) {
442 $haystack .= wp_strip_all_tags( $f->post_content );
443 }
444 $haystack = function_exists( 'mb_strtolower' ) ? mb_strtolower( $haystack ) : strtolower( $haystack );
445 return false !== strpos( $haystack, $search_term_lc );
446 }
447 )
448 );
449 }
450
451 // Classify followers only when a filter is active.
452 if ( 'all' !== $filter ) {
453 $following_ids = array();
454 $not_following_ids = array();
455 foreach ( $follower_data['followers'] as $follower ) {
456 $url = $follower->guid;
457 $is_following = false;
458 if ( $url ) {
459 $is_following = User_Feed::get_by_url( $url );
460 if ( ! $is_following || is_wp_error( $is_following ) ) {
461 $is_following = User_Feed::get_by_url( str_replace( '@', 'users/', $url ) );
462 }
463 }
464 if ( $is_following && ! is_wp_error( $is_following ) ) {
465 $following_ids[] = $follower->ID;
466 } else {
467 $not_following_ids[] = $follower->ID;
468 }
469 }
470
471 if ( 'following' === $filter ) {
472 $filtered_followers = array_filter(
473 $follower_data['followers'],
474 function ( $f ) use ( $following_ids ) {
475 return in_array( $f->ID, $following_ids, true );
476 }
477 );
478 } else {
479 $filtered_followers = array_filter(
480 $follower_data['followers'],
481 function ( $f ) use ( $not_following_ids ) {
482 return in_array( $f->ID, $not_following_ids, true );
483 }
484 );
485 }
486 } else {
487 $filtered_followers = $follower_data['followers'];
488 }
489
490 // Sort.
491 $filtered_followers = array_values( $filtered_followers );
492 if ( 'oldest' === $sort ) {
493 usort(
494 $filtered_followers,
495 function ( $a, $b ) {
496 return $a->ID - $b->ID;
497 }
498 );
499 } elseif ( 'name' === $sort ) {
500 usort(
501 $filtered_followers,
502 function ( $a, $b ) {
503 return strnatcasecmp( $a->post_title, $b->post_title );
504 }
505 );
506 }
507
508 // Paginate.
509 $filtered_total = count( $filtered_followers );
510 $followers_per_page = 20;
511 $current_page = isset( $_GET['fpage'] ) ? max( 1, absint( $_GET['fpage'] ) ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
512 $page_followers = array_slice( $filtered_followers, ( $current_page - 1 ) * $followers_per_page, $followers_per_page );
513 $display_followers = array();
514
515 foreach ( $page_followers as $follower ) {
516 if ( $follower instanceof \WP_Post ) {
517 $follower = \Activitypub\Collection\Remote_Actors::get_actor( $follower );
518 if ( is_wp_error( $follower ) ) {
519 continue;
520 }
521 }
522 $data = $follower->to_array();
523 $data['url'] = \ActivityPub\object_to_uri( $data['url'] );
524 $data['server'] = wp_parse_url( $data['url'], PHP_URL_HOST );
525 $data['css_class'] = '';
526
527 $following = User_Feed::get_by_url( $data['url'] );
528 if ( ! $following || is_wp_error( $following ) ) {
529 $following = User_Feed::get_by_url( str_replace( '@', 'users/', $data['url'] ) );
530 }
531
532 if ( $following && ! is_wp_error( $following ) ) {
533 $data['friend_user'] = $following->get_friend_user();
534 $data['action_url'] = $following->get_friend_user()->get_local_friends_page_url();
535 $data['url'] = $following->get_friend_user()->get_local_friends_page_url();
536 if ( 'all' === $filter ) {
537 $data['css_class'] = ' already-following';
538 }
539 } else {
540 $data['friend_user'] = false;
541 $data['action_url'] = add_query_arg( 'url', $data['url'], admin_url( 'admin.php?page=add-friend' ) );
542 }
543 $data['remove_action_url'] = add_query_arg( 's', $data['url'], admin_url( $followers_list_page ) );
544 $display_followers[] = $data;
545 }
546
547 $base_url = strtok( $_SERVER['REQUEST_URI'], '?' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
548 $link_args = array();
549 if ( 'all' !== $filter ) {
550 $link_args['filter'] = $filter;
551 }
552 if ( 'newest' !== $sort ) {
553 $link_args['sort'] = $sort;
554 }
555 if ( '' !== $search_term ) {
556 $link_args['q'] = $search_term;
557 }
558
559 ob_start();
560 ?>
561 <section class="followers">
562 <p>
563 <?php
564 if ( $blog_followers ) {
565 echo esc_html(
566 sprintf(
567 // translators: %s is the number of followers.
568 _n( 'You have %s blog follower.', 'You have %s blog followers.', $total, 'friends' ),
569 $total
570 )
571 );
572 } else {
573 echo esc_html(
574 sprintf(
575 // translators: %s is the number of followers.
576 _n( 'You have %s follower.', 'You have %s followers.', $total, 'friends' ),
577 $total
578 )
579 );
580 }
581
582 ?>
583 <a href="<?php echo esc_url( admin_url( $followers_list_page ) ); ?>">
584 <?php
585 if ( $blog_followers ) {
586 esc_html_e( 'View all blog followers in wp-admin', 'friends' );
587 } else {
588 esc_html_e( 'View all followers in wp-admin', 'friends' );
589 }
590 ?>
591 </a>
592 </p>
593 <form method="get" action="<?php echo esc_url( $base_url ); ?>" class="friends-search" role="search">
594 <label class="friends-search-label">
595 <?php esc_html_e( 'Search:', 'friends' ); ?>
596 <input type="search" name="q" value="<?php echo esc_attr( $search_term ); ?>" placeholder="<?php esc_attr_e( 'Name, handle, or description', 'friends' ); ?>" class="friends-search-input" />
597 </label>
598 <button type="submit" class="button friends-search-submit"><?php esc_html_e( 'Search', 'friends' ); ?></button>
599 <?php if ( 'all' !== $filter ) : ?>
600 <input type="hidden" name="filter" value="<?php echo esc_attr( $filter ); ?>" />
601 <?php endif; ?>
602 <?php if ( 'newest' !== $sort ) : ?>
603 <input type="hidden" name="sort" value="<?php echo esc_attr( $sort ); ?>" />
604 <?php endif; ?>
605 <?php
606 if ( '' !== $search_term ) :
607 $clear_args = $link_args;
608 unset( $clear_args['q'] );
609 $clear_url = $clear_args ? add_query_arg( $clear_args, $base_url ) : $base_url;
610 ?>
611 <a href="<?php echo esc_url( $clear_url ); ?>" class="friends-search-clear"><?php esc_html_e( 'Clear', 'friends' ); ?></a>
612 <?php endif; ?>
613 </form>
614 <?php if ( '' !== $search_term ) : ?>
615 <p>
616 <?php
617 echo esc_html(
618 sprintf(
619 // translators: %1$s is the number of matches, %2$s is the search term.
620 _n( '%1$s match for "%2$s"', '%1$s matches for "%2$s"', count( $filtered_followers ), 'friends' ),
621 number_format_i18n( count( $filtered_followers ) ),
622 $search_term
623 )
624 );
625 ?>
626 </p>
627 <?php endif; ?>
628 <p>
629 <?php esc_html_e( 'Filter:', 'friends' ); ?>
630 <?php
631 $filters = array(
632 'all' => __( 'All', 'friends' ),
633 'following' => __( 'Following', 'friends' ),
634 'not-following' => __( 'Not Following', 'friends' ),
635 );
636 $filter_links = array();
637 foreach ( $filters as $filter_key => $filter_label ) {
638 $url = $base_url;
639 $f_args = $link_args;
640 if ( 'all' !== $filter_key ) {
641 $f_args['filter'] = $filter_key;
642 } else {
643 unset( $f_args['filter'] );
644 }
645 if ( $f_args ) {
646 $url = add_query_arg( $f_args, $url );
647 }
648 if ( $filter === $filter_key ) {
649 $filter_links[] = '<strong>' . esc_html( $filter_label ) . '</strong>';
650 } else {
651 $filter_links[] = '<a href="' . esc_url( $url ) . '">' . esc_html( $filter_label ) . '</a>';
652 }
653 }
654 echo wp_kses(
655 implode( ' | ', $filter_links ),
656 array(
657 'strong' => array(),
658 'a' => array( 'href' => array() ),
659 )
660 );
661 ?>
662
663 &nbsp;&nbsp;
664 <?php esc_html_e( 'Sort:', 'friends' ); ?>
665 <?php
666 $sorts = array(
667 'newest' => __( 'Newest', 'friends' ),
668 'oldest' => __( 'Oldest', 'friends' ),
669 'name' => __( 'Name', 'friends' ),
670 );
671 $sort_links = array();
672 foreach ( $sorts as $sort_key => $sort_label ) {
673 $url = $base_url;
674 $s_args = $link_args;
675 if ( 'newest' !== $sort_key ) {
676 $s_args['sort'] = $sort_key;
677 } else {
678 unset( $s_args['sort'] );
679 }
680 if ( $s_args ) {
681 $url = add_query_arg( $s_args, $url );
682 }
683 if ( $sort === $sort_key ) {
684 $sort_links[] = '<strong>' . esc_html( $sort_label ) . '</strong>';
685 } else {
686 $sort_links[] = '<a href="' . esc_url( $url ) . '">' . esc_html( $sort_label ) . '</a>';
687 }
688 }
689 echo wp_kses(
690 implode( ' | ', $sort_links ),
691 array(
692 'strong' => array(),
693 'a' => array( 'href' => array() ),
694 )
695 );
696 ?>
697 </p>
698 <ul>
699 <?php foreach ( $display_followers as $follower ) : ?>
700 <li>
701 <details data-nonce="<?php echo esc_attr( wp_create_nonce( 'friends-preview' ) ); ?>" data-following="<?php echo esc_attr( $follower['following'] ); ?>" data-followers="<?php echo esc_attr( $follower['followers'] ); ?>" data-id="<?php echo esc_attr( $follower['id'] ); ?>">
702 <summary>
703 <a href="<?php echo esc_url( $follower['url'] ); ?>" class="follower<?php echo esc_attr( $follower['css_class'] ); ?>">
704 <img width="40" height="40" src="<?php echo esc_attr( $follower['icon']['url'] ); ?>" loading="lazy" class="avatar activitypub-avatar" />
705 <span class="activitypub-actor">
706 <strong class="activitypub-name"><?php echo esc_html( $follower['name'] ); ?></strong>
707 (<span class="activitypub-handle">@<?php echo esc_html( $follower['preferredUsername'] . '@' . $follower['server'] ); ?></span>)
708 </span>
709 </a>
710 <span class="since"><?php echo esc_html( $follower['published'] ); ?></span>
711 <span class="their-followers"></span>
712 <span class="their-following"></span>
713 &nbsp;&nbsp;
714 <?php if ( $follower['friend_user'] ) : ?>
715 <span class="follower" title="<?php esc_attr_e( 'Already following', 'friends' ); ?>">
716 <span class="ab-icon dashicons dashicons-businessperson" style="vertical-align: middle;"><span class="ab-icon dashicons dashicons-yes"></span></span>
717 </span>
718 <?php else : ?>
719 <a href="<?php echo esc_url( $follower['action_url'] ); ?>" class="follower follower-add">
720 <span class="ab-icon dashicons dashicons-businessperson" style="vertical-align: middle;"><span class="ab-icon dashicons dashicons-plus"></span></span>
721 </a>
722 <?php endif; ?>
723 <a href="<?php echo esc_url( $follower['remove_action_url'] ); ?>" class="follower follower-delete" title="<?php esc_attr_e( 'Remove follower', 'friends' ); ?>" data-nonce="<?php echo esc_attr( wp_create_nonce( 'friends-followers' ) ); ?>" data-handle="<?php echo esc_attr( $follower['preferredUsername'] . '@' . $follower['server'] ); ?>" data-id="<?php echo esc_attr( $follower['id'] ); ?>">
724 <span class="ab-icon dashicons dashicons-admin-users" style="vertical-align: middle;"><span class="ab-icon dashicons dashicons-no"></span></span>
725 </a>
726 <p class="description">
727 <?php
728 echo wp_kses(
729 $follower['summary'],
730 array(
731 'a' => array( 'href' => array() ),
732 )
733 );
734 ?>
735 </p>
736 </summary>
737 <p class="loading-posts">
738 <span><?php esc_html_e( 'Loading posts', 'friends' ); ?></span>
739 <i class="form-icon loading"></i>
740 </p>
741 </details>
742 </li>
743 <?php endforeach; ?>
744 </ul>
745 <?php
746 $total_pages = ceil( $filtered_total / $followers_per_page );
747 if ( $total_pages > 1 ) {
748 $pagination_args = array(
749 'base' => add_query_arg( 'fpage', '%#%' ),
750 'format' => '',
751 'current' => $current_page,
752 'total' => $total_pages,
753 );
754 if ( $link_args ) {
755 $pagination_args['add_args'] = $link_args;
756 }
757 echo '<nav class="pagination">';
758 echo wp_kses_post( paginate_links( $pagination_args ) );
759 echo '</nav>';
760 }
761 ?>
762 </section>
763 <?php
764 return ob_get_clean();
765 }
766
767 /**
768 * Render the friends/stats block.
769 *
770 * @param array $attributes Block attributes.
771 * @return string The rendered block HTML.
772 */
773 public function render_stats_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
774 $subscriptions = User_Query::all_subscriptions();
775 $subscriptions_count = $subscriptions->get_total();
776
777 $out = '<ul ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-stats' ) ) . '>';
778
779 if ( class_exists( '\ActivityPub\Collection\Followers' ) && \defined( 'ACTIVITYPUB_ACTOR_MODE' ) ) {
780 $activitypub_actor_mode = \get_option( 'activitypub_actor_mode', \ACTIVITYPUB_ACTOR_MODE );
781 if ( \ACTIVITYPUB_ACTOR_MODE === $activitypub_actor_mode || \ACTIVITYPUB_ACTOR_AND_BLOG_MODE === $activitypub_actor_mode ) {
782 $mutual_count = Feed_Parser_ActivityPub::count_mutual_followers( get_current_user_id() );
783 $out .= '<li><a href="' . esc_url( home_url( '/friends/mutual/' ) ) . '">';
784 $out .= esc_html(
785 sprintf(
786 /* translators: %s: number of mutual friends */
787 _n( '%s Friend', '%s Friends', $mutual_count, 'friends' ),
788 $mutual_count
789 )
790 );
791 $out .= '</a></li>';
792
793 $follower_count = Feed_Parser_ActivityPub::count_followers( get_current_user_id() );
794 $out .= '<li><a href="' . esc_url( home_url( '/friends/followers/' ) ) . '">';
795 $out .= esc_html(
796 sprintf(
797 /* translators: %s: number of followers */
798 _n( '%s Follower', '%s Followers', $follower_count, 'friends' ),
799 $follower_count
800 )
801 );
802 $out .= '</a></li>';
803 }
804 if ( \ACTIVITYPUB_BLOG_MODE === $activitypub_actor_mode || \ACTIVITYPUB_ACTOR_AND_BLOG_MODE === $activitypub_actor_mode ) {
805 if ( class_exists( '\ActivityPub\Collection\Actors' ) ) {
806 $blog_follower_count = Feed_Parser_ActivityPub::count_followers( \ActivityPub\Collection\Actors::BLOG_USER_ID );
807 $out .= '<li><a href="' . esc_url( home_url( '/friends/blog-followers/' ) ) . '">';
808 $out .= esc_html(
809 sprintf(
810 /* translators: %s: number of followers */
811 _n( '%s Blog Follower', '%s Blog Followers', $blog_follower_count, 'friends' ),
812 $blog_follower_count
813 )
814 );
815 $out .= '</a></li>';
816 }
817 }
818 }
819
820 $out .= '<li><a href="' . esc_url( home_url( '/friends/following/' ) ) . '">';
821 $out .= esc_html(
822 sprintf(
823 /* translators: %s: number of subscriptions */
824 _n( '%s Following', '%s Following', $subscriptions_count, 'friends' ),
825 $subscriptions_count
826 )
827 );
828 $out .= '</a></li>';
829 $out .= '</ul>';
830
831 return $out;
832 }
833
834 /**
835 * Render the friends/refresh block.
836 *
837 * @param array $attributes Block attributes.
838 * @return string The rendered block HTML.
839 */
840 public function render_refresh_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
841 return '<p ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-refresh' ) ) . '><a href="' . esc_url( home_url( '/friends/?refresh' ) ) . '">' . esc_html__( 'Refresh', 'friends' ) . '</a></p>';
842 }
843
844 /**
845 * Render the friends/post-formats block.
846 *
847 * @param array $attributes Block attributes.
848 * @return string The rendered block HTML.
849 */
850 public function render_post_formats_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
851 $out = '<ul ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-post-formats' ) ) . '>';
852 $out .= '<li><a href="' . esc_url( home_url( '/friends/' ) ) . '">' . esc_html_x( 'All', 'all posts', 'friends' ) . '</a></li>';
853
854 $default_formats = array( 'standard', 'status', 'image', 'video' );
855 foreach ( get_post_format_strings() as $slug => $title ) {
856 if ( in_array( $slug, $default_formats, true ) ) {
857 $out .= '<li><a href="' . esc_url( home_url( '/friends/type/' . $slug . '/' ) ) . '">' . esc_html( $title ) . '</a></li>';
858 }
859 }
860
861 $out .= '</ul>';
862 return $out;
863 }
864
865 /**
866 * Render the friends/add-subscription block.
867 *
868 * @param array $attributes Block attributes.
869 * @return string The rendered block HTML.
870 */
871 public function render_add_subscription_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
872 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-add-subscription' ) ) . '>';
873 $out .= '<a href="' . esc_url( admin_url( 'admin.php?page=add-friend' ) ) . '">';
874 $out .= esc_html__( 'Follow', 'friends' );
875 $out .= '</a></div>';
876 return $out;
877 }
878
879 /**
880 * Render the friends/search block.
881 *
882 * @return string The rendered block HTML.
883 */
884 public function render_search_block() {
885 $search = isset( $_GET['s'] ) ? sanitize_text_field( wp_unslash( $_GET['s'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
886 $out = '<div class="wp-block-friends-search">';
887 $out .= '<form action="' . esc_url( home_url( '/friends/' ) ) . '">';
888 $out .= '<input type="text" name="s" placeholder="' . esc_attr__( 'Search or paste URL', 'friends' ) . '" value="' . esc_attr( $search ) . '" autocomplete="off" data-nonce="' . esc_attr( wp_create_nonce( 'friends-autocomplete' ) ) . '" />';
889 $out .= ' <button type="submit">' . esc_html__( 'Search', 'friends' ) . '</button>';
890 $out .= '</form></div>';
891 return $out;
892 }
893
894 /**
895 * Render the friends/feed-header block.
896 *
897 * @return string The rendered block HTML.
898 */
899 /**
900 * Render the friends/feed-title block.
901 *
902 * @return string The rendered block HTML.
903 */
904 public function render_feed_title_block() {
905 $friends = Friends::get_instance();
906 $frontend = $friends->frontend;
907
908 // Determine feed title.
909 $title = __( 'Main Feed', 'friends' );
910 if ( $frontend->template ) {
911 $template_titles = array(
912 'frontend/add-friend' => __( 'Add Friend', 'friends' ),
913 'frontend/followers' => __( 'Followers', 'friends' ),
914 'frontend/subscriptions' => __( 'Following', 'friends' ),
915 );
916 if ( isset( $template_titles[ $frontend->template ] ) ) {
917 $title = $template_titles[ $frontend->template ];
918 }
919 }
920
921 $format_titles = array(
922 'standard' => _x( 'Post feed', 'Post format', 'friends' ),
923 'aside' => _x( 'Aside feed', 'Post format', 'friends' ),
924 'chat' => _x( 'Chat feed', 'Post format', 'friends' ),
925 'gallery' => _x( 'Gallery feed', 'Post format', 'friends' ),
926 'link' => _x( 'Link feed', 'Post format', 'friends' ),
927 'image' => _x( 'Image feed', 'Post format', 'friends' ),
928 'quote' => _x( 'Quote feed', 'Post format', 'friends' ),
929 'status' => _x( 'Status feed', 'Post format', 'friends' ),
930 'video' => _x( 'Video feed', 'Post format', 'friends' ),
931 'audio' => _x( 'Audio feed', 'Post format', 'friends' ),
932 );
933 if ( $frontend->post_format && isset( $format_titles[ $frontend->post_format ] ) ) {
934 $title = $format_titles[ $frontend->post_format ];
935 }
936
937 if ( isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
938 $title = sprintf(
939 // translators: %s is a search term.
940 __( 'Search for "%s"', 'friends' ),
941 esc_html( sanitize_text_field( wp_unslash( $_GET['s'] ) ) ) // phpcs:ignore WordPress.Security.NonceVerification.Recommended
942 );
943 }
944
945 // Build display title with reaction/tag context.
946 $display_title = $title;
947 if ( $frontend->reaction ) {
948 $display_title = sprintf(
949 // translators: %1$s is an emoji reaction, %2$s is a type of feed.
950 __( 'My %1$s reactions on %2$s', 'friends' ),
951 $frontend->reaction,
952 $title
953 );
954 } elseif ( $frontend->tag ) {
955 $display_title = sprintf(
956 // translators: %1$s is a hash tag, %2$s is a type of feed.
957 _x( '#%1$s on %2$s', '#tag on feed', 'friends' ),
958 $frontend->tag,
959 $title
960 );
961 }
962
963 return '<h2 ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-feed-title' ) ) . '><a href="' . esc_url( home_url( '/friends/' ) ) . '">' . esc_html( $display_title ) . '</a></h2>';
964 }
965
966 /**
967 * Render the friends/feed-chips block.
968 *
969 * @param array $attributes Block attributes.
970 * @return string The rendered block HTML.
971 */
972 public function render_feed_chips_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
973 $friends = Friends::get_instance();
974 $data = $friends->get_main_header_data();
975
976 $hidden_post_count = 0;
977 if ( isset( $data['post_count_by_post_status']->trash ) ) {
978 $hidden_post_count = $data['post_count_by_post_status']->trash;
979 }
980
981 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-feed-chips' ) ) . '>';
982
983 if ( isset( $_GET['s'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
984 $search_term = sanitize_text_field( wp_unslash( $_GET['s'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
985 $current_order = isset( $_GET['order'] ) ? strtoupper( sanitize_text_field( wp_unslash( $_GET['order'] ) ) ) : 'DESC'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
986
987 // Clear search chip.
988 $out .= '<a class="chip" href="' . esc_url( remove_query_arg( 's' ) ) . '">';
989 $out .= esc_html( sprintf( /* translators: %s is the search term */ __( 'Clear search for "%s"', 'friends' ), $search_term ) );
990 $out .= '</a> ';
991
992 // Order chips.
993 if ( 'DESC' === $current_order ) {
994 $out .= '<a class="chip" href="' . esc_url( add_query_arg( 'order', 'ASC' ) ) . '">' . esc_html__( 'Oldest first', 'friends' ) . '</a> ';
995 } else {
996 $out .= '<a class="chip active" href="' . esc_url( add_query_arg( 'order', 'ASC' ) ) . '">' . esc_html__( 'Oldest first', 'friends' ) . '</a> ';
997 $out .= '<a class="chip" href="' . esc_url( remove_query_arg( 'order' ) ) . '">' . esc_html__( 'Newest first', 'friends' ) . '</a> ';
998 }
999 } else {
1000 // Post count chips.
1001 $nonce = wp_create_nonce( 'friends_post_counts' );
1002 foreach ( $data['post_count_by_post_format'] as $post_format => $count ) {
1003 $out .= '<a class="chip post-count-' . esc_attr( $post_format ) . '" data-nonce="' . esc_attr( $nonce ) . '" href="' . esc_url( home_url( '/friends/type/' . $post_format . '/' ) ) . '">' . esc_html( $friends->get_post_format_plural_string( $post_format, $count ) ) . '</a> ';
1004 }
1005
1006 // Hidden items chip.
1007 if ( isset( $_GET['show-hidden'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1008 $out .= '<a class="chip" href="' . esc_url( remove_query_arg( 'show-hidden' ) ) . '">' . esc_html__( 'Hide hidden items', 'friends' ) . '</a> ';
1009 } elseif ( $hidden_post_count > 0 ) {
1010 $out .= '<a class="chip" href="' . esc_url( add_query_arg( 'show-hidden', 1 ) ) . '">';
1011 $out .= esc_html( sprintf( /* translators: %s is the number of hidden posts */ _n( '%s hidden items', '%s hidden items', $hidden_post_count, 'friends' ), number_format_i18n( $hidden_post_count ) ) );
1012 $out .= '</a> ';
1013 }
1014
1015 // Reaction chips.
1016 if ( class_exists( __NAMESPACE__ . '\Reactions' ) ) {
1017 foreach ( Reactions::get_available_emojis() as $slug => $reaction ) {
1018 $out .= '<a class="chip" href="' . esc_url( home_url( '/friends/reaction' . $slug . '/' ) ) . '">';
1019 $out .= esc_html( sprintf( /* translators: %s is an emoji */ __( 'Reacted with %s', 'friends' ), $reaction->char ) );
1020 $out .= '</a> ';
1021 }
1022 }
1023 }
1024
1025 $out .= '</div>';
1026 return $out;
1027 }
1028
1029 /**
1030 * Render the friends/welcome block.
1031 *
1032 * @param array $attributes Block attributes.
1033 * @return string The rendered block HTML.
1034 */
1035 public function render_welcome_block( $attributes = array() ) {
1036 $friends = Friends::get_instance();
1037 $args = array(
1038 'friends' => $friends,
1039 'post_format' => $friends->frontend->post_format,
1040 );
1041
1042 ob_start();
1043 if ( empty( $attributes['forceWelcome'] ) && User_Query::all_associated_users()->get_total() > 0 ) {
1044 Friends::template_loader()->get_template_part( 'frontend/no-posts', $args['post_format'], $args );
1045 } else {
1046 Friends::template_loader()->get_template_part( 'frontend/no-friends', $args['post_format'], $args );
1047 }
1048 $content = ob_get_clean();
1049
1050 return '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-welcome friends-empty-state' ) ) . '>' . $content . '</div>';
1051 }
1052
1053 /**
1054 * Render the friends/post-content block.
1055 *
1056 * Renders friend post content directly without block parsing.
1057 *
1058 * @return string The rendered block HTML.
1059 */
1060 public function render_post_content_block() {
1061 global $post;
1062 if ( ! $post ) {
1063 return '<div class="wp-block-friends-post-content"><p><em>' . esc_html__( 'Post content will appear here.', 'friends' ) . '</em></p></div>';
1064 }
1065
1066 $content = get_the_content();
1067 $content = wp_kses_post( $content );
1068 $content = wpautop( $content );
1069
1070 return '<div class="wp-block-friends-post-content">' . $content . '</div>';
1071 }
1072
1073 /**
1074 * Render the friends/post-permalink block.
1075 *
1076 * Shows "X ago on domain.com Y min read" for each post.
1077 *
1078 * @param array $attributes Block attributes.
1079 * @return string The rendered block HTML.
1080 */
1081 public function render_post_permalink_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1082 global $post;
1083 if ( ! $post ) {
1084 return '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-post-permalink' ) ) . '><em>' . esc_html__( '3 days ago on example.com', 'friends' ) . ' <span class="reading-time">' . esc_html__( '2 min read', 'friends' ) . '</span></em></div>';
1085 }
1086
1087 $friend_user = User::get_post_author( $post );
1088 if ( ! $friend_user || is_wp_error( $friend_user ) ) {
1089 return '';
1090 }
1091
1092 $author_url = $friend_user->get_local_friends_page_url();
1093 $guid = get_the_guid( $post );
1094 $domain = wp_parse_url( $guid, PHP_URL_HOST );
1095 $local_url = $author_url . $post->ID . '/';
1096
1097 // Reading time.
1098 $read_time_seconds = Frontend::calculate_read_time( get_the_content() );
1099 $read_time = '';
1100 if ( $read_time_seconds >= 60 ) {
1101 $mins = ceil( $read_time_seconds / MINUTE_IN_SECONDS );
1102 /* translators: Time difference between two dates, in minutes (min=minute). %s: Number of minutes. */
1103 $read_time = sprintf( _n( '%s min', '%s mins', $mins ), $mins ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1104 } elseif ( $read_time_seconds > 20 ) {
1105 $read_time = _x( '< 1 min', 'reading time', 'friends' );
1106 }
1107
1108 /* translators: %s is a time span */
1109 $time_ago = sprintf( __( '%s ago' ), human_time_diff( get_post_time( 'U', true ) ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1110 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-post-permalink' ) ) . '>';
1111 $out .= sprintf(
1112 // translators: %1$s is a date or relative time, %2$s is a site name or domain.
1113 _x( '%1$s on %2$s', 'at-date-on-post', 'friends' ),
1114 '<a href="' . esc_url( $local_url ) . '" title="' . esc_attr( get_the_time( 'r' ) ) . '">' . esc_html( $time_ago ) . '</a>',
1115 '<a href="' . esc_url( $guid ) . '" rel="noopener noreferrer" target="_blank">' . esc_html( $domain ) . '</a>'
1116 );
1117 if ( $read_time ) {
1118 $out .= ' <span class="reading-time" title="' . esc_attr__( 'Estimated reading time', 'friends' ) . '">';
1119 $out .= esc_html( sprintf( /* translators: %s is a timeframe */ __( '%s read', 'friends' ), $read_time ) );
1120 $out .= '</span>';
1121 }
1122 $out .= '</div>';
1123
1124 return $out;
1125 }
1126
1127 /**
1128 * Render the friends/post-reblog block.
1129 *
1130 * @return string The rendered block HTML.
1131 */
1132 public function render_post_reblog_block() {
1133 global $post;
1134 if ( ! $post ) {
1135 return '<span class="wp-block-friends-post-reblog">' . esc_html_x( 'Reblog', 'button', 'friends' ) . '</span>';
1136 }
1137
1138 $friend_user = User::get_post_author( $post );
1139 if ( $friend_user && ! is_wp_error( $friend_user ) && get_current_user_id() === $friend_user->ID ) {
1140 return '';
1141 }
1142
1143 $post_id = get_the_ID();
1144 $reblog_nonce = wp_create_nonce( 'friends-reblog' );
1145 $reblog_status = get_post_meta( $post_id, 'reblogged', true ) ? ' dashicons-saved' : '';
1146
1147 $out = '<span class="wp-block-friends-post-reblog">';
1148 $out .= '<a tabindex="0" href="#" data-id="' . esc_attr( $post_id ) . '" data-nonce="' . esc_attr( $reblog_nonce ) . '" class="friends-reblog has-icon-right" title="' . esc_attr_x( 'Reblog', 'button', 'friends' ) . '">';
1149 $out .= '<i class="dashicons dashicons-controls-repeat"></i> <span class="text">' . esc_html_x( 'Reblog', 'button', 'friends' ) . '</span>';
1150 $out .= '<i class="friends-reblog-status dashicons' . esc_attr( $reblog_status ) . '"></i>';
1151 $out .= '</a></span>';
1152
1153 return $out;
1154 }
1155
1156 /**
1157 * Render the friends/post-boost block.
1158 *
1159 * @return string The rendered block HTML.
1160 */
1161 public function render_post_boost_block() {
1162 global $post;
1163 if ( ! $post ) {
1164 return '<span class="wp-block-friends-post-boost">' . esc_html_x( 'Boost', 'button', 'friends' ) . '</span>';
1165 }
1166
1167 if ( ! has_filter( 'friends_boost' ) && ! class_exists( '\Activitypub\Activitypub' ) ) {
1168 return '';
1169 }
1170
1171 $friend_user = User::get_post_author( $post );
1172 if ( $friend_user && ! is_wp_error( $friend_user ) && get_current_user_id() === $friend_user->ID ) {
1173 return '';
1174 }
1175
1176 $post_id = get_the_ID();
1177 $boost_nonce = wp_create_nonce( 'friends-boost' );
1178 $boost_status = get_post_meta( $post_id, 'boosted', true ) ? ' dashicons-saved' : '';
1179
1180 $out = '<span class="wp-block-friends-post-boost">';
1181 $out .= '<a tabindex="0" href="#" data-id="' . esc_attr( $post_id ) . '" data-nonce="' . esc_attr( $boost_nonce ) . '" class="friends-boost has-icon-right" title="' . esc_attr_x( 'Boost', 'button', 'friends' ) . '">';
1182 $out .= '<i class="dashicons dashicons-controls-repeat"></i> <span class="text">' . esc_html_x( 'Boost', 'button', 'friends' ) . '</span>';
1183 $out .= '<i class="friends-boost-status dashicons' . esc_attr( $boost_status ) . '"></i>';
1184 $out .= '</a></span>';
1185
1186 return $out;
1187 }
1188
1189 /**
1190 * Render the friends/post-reactions block.
1191 *
1192 * @return string The rendered block HTML.
1193 */
1194 public function render_post_reactions_block() {
1195 global $post;
1196 if ( ! $post ) {
1197 return '<span class="wp-block-friends-post-reactions">⭐ ❤️</span>';
1198 }
1199
1200 if ( ! class_exists( __NAMESPACE__ . '\Reactions' ) ) {
1201 return '';
1202 }
1203
1204 $post_id = get_the_ID();
1205 $reactions = Reactions::get_post_reactions();
1206 $reaction_nonce = wp_create_nonce( 'friends-reaction' );
1207
1208 $out = '<span class="wp-block-friends-post-reactions">';
1209
1210 foreach ( $reactions as $slug => $reaction ) {
1211 $pressed = $reaction->user_reacted ? ' pressed' : '';
1212 $out .= '<button class="friends-reaction' . esc_attr( $pressed ) . '" data-id="' . esc_attr( $post_id ) . '" data-emoji="' . esc_attr( $slug ) . '" data-nonce="' . esc_attr( $reaction_nonce ) . '" title="' . esc_attr( $reaction->usernames ) . '">';
1213 $out .= '<span>' . esc_html( $reaction->emoji ) . '</span> ' . esc_html( $reaction->count );
1214 $out .= '</button> ';
1215 }
1216
1217 // Render available emojis as inline buttons for reactions not yet used.
1218 $available = Reactions::get_available_emojis();
1219 foreach ( $available as $slug => $emoji ) {
1220 if ( isset( $reactions[ $slug ] ) ) {
1221 continue;
1222 }
1223 $out .= '<button class="friends-reaction-picker" data-id="' . esc_attr( $post_id ) . '" data-emoji="' . esc_attr( $slug ) . '" data-nonce="' . esc_attr( $reaction_nonce ) . '">' . esc_html( $emoji->char ) . '</button> ';
1224 }
1225
1226 $out .= '</span>';
1227 return $out;
1228 }
1229
1230 /**
1231 * Render the friends/post-comments block.
1232 *
1233 * @return string The rendered block HTML.
1234 */
1235 public function render_post_comments_block() {
1236 global $post;
1237 if ( ! $post ) {
1238 return '<span class="wp-block-friends-post-comments">💬 ' . esc_html__( 'Comments' ) . '</span>'; // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1239 }
1240
1241 $post_id = get_the_ID();
1242 $has_mention = get_post_meta( $post_id, '_has_mention_in_comments', true );
1243 $comment_icon = $has_mention ? 'format-status' : 'admin-comments';
1244 $comment_text = $has_mention ? __( 'Comments (You were mentioned)', 'friends' ) : __( 'Comments' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1245
1246 $out = '<div class="wp-block-friends-post-comments">';
1247 $out .= '<a href="#" class="comments" data-id="' . esc_attr( $post_id ) . '" data-cnonce="' . esc_attr( wp_create_nonce( 'comments-' . $post_id ) ) . '">';
1248 $out .= '<i class="dashicons dashicons-' . esc_attr( $comment_icon ) . '"></i> <span class="text">' . esc_html( $comment_text ) . '</span>';
1249 $out .= '</a>';
1250 $out .= '<div class="comments-content closed"></div>';
1251 $out .= '</div>';
1252
1253 return $out;
1254 }
1255
1256 /**
1257 * Get the current author from the frontend context.
1258 *
1259 * @return User|null
1260 */
1261 private function get_frontend_author() {
1262 $friends = Friends::get_instance();
1263 return $friends->frontend->author;
1264 }
1265
1266 /**
1267 * Render the friends/author-star block.
1268 *
1269 * @return string The rendered block HTML.
1270 */
1271 public function render_author_star_block() {
1272 $author = $this->get_frontend_author();
1273 if ( ! $author ) {
1274 return '<span class="wp-block-friends-author-star">&#9734;</span>';
1275 }
1276
1277 $starred = $author->is_starred();
1278 $star_class = $starred ? 'dashicons-star-filled starred' : 'dashicons-star-empty not-starred';
1279 $star_nonce = wp_create_nonce( 'star-' . $author->user_login );
1280
1281 return '<a href="" class="wp-block-friends-author-star dashicons ' . esc_attr( $star_class ) . '" data-id="' . esc_attr( $author->user_login ) . '" data-nonce="' . esc_attr( $star_nonce ) . '"></a>';
1282 }
1283
1284 /**
1285 * Render the friends/author-avatar block.
1286 *
1287 * @return string The rendered block HTML.
1288 */
1289 public function render_author_avatar_block() {
1290 $author = $this->get_frontend_author();
1291 if ( ! $author ) {
1292 return '<span class="wp-block-friends-author-avatar"></span>';
1293 }
1294
1295 $avatar_url = $author->get_avatar_url();
1296 if ( ! $avatar_url ) {
1297 return '<span class="wp-block-friends-author-avatar"></span>';
1298 }
1299
1300 return '<img class="wp-block-friends-author-avatar" src="' . esc_url( $avatar_url ) . '" width="36" height="36" />';
1301 }
1302
1303 /**
1304 * Render the friends/author-name block.
1305 *
1306 * @param array $attributes Block attributes.
1307 * @return string The rendered block HTML.
1308 */
1309 public function render_author_name_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1310 $author = $this->get_frontend_author();
1311 if ( ! $author ) {
1312 return '<h2 ' . $this->get_wrapper_attributes(
1313 array(
1314 'class' => 'wp-block-friends-author-name',
1315 'id' => 'page-title',
1316 )
1317 ) . '>' . esc_html__( 'Author Name', 'friends' ) . '</h2>';
1318 }
1319
1320 return '<h2 ' . $this->get_wrapper_attributes(
1321 array(
1322 'class' => 'wp-block-friends-author-name',
1323 'id' => 'page-title',
1324 )
1325 ) . '>' . wp_kses( Feed_Parser_ActivityPub::replace_custom_emojis_for_user( $author->display_name, $author ), Feed_Parser_ActivityPub::get_custom_emoji_allowed_html() ) . '</h2>';
1326 }
1327
1328 /**
1329 * Render the friends/author-description block.
1330 *
1331 * @param array $attributes Block attributes.
1332 * @return string The rendered block HTML.
1333 */
1334 public function render_author_description_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1335 $author = $this->get_frontend_author();
1336 if ( ! $author ) {
1337 return '<p ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-description' ) ) . '><em>' . esc_html__( 'Author description will appear here.', 'friends' ) . '</em></p>';
1338 }
1339 if ( ! $author->description ) {
1340 return '';
1341 }
1342
1343 return '<p ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-description' ) ) . '>' . wp_kses( $author->description, array( 'a' => array( 'href' => array() ) ) ) . '</p>';
1344 }
1345
1346 /**
1347 * Render the friends/author-chips block.
1348 *
1349 * @param array $attributes Block attributes.
1350 * @return string The rendered block HTML.
1351 */
1352 public function render_author_chips_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1353 $friends = Friends::get_instance();
1354 $author = $this->get_frontend_author();
1355 if ( ! $author ) {
1356 return '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-chips' ) ) . '><span class="chip">' . esc_html__( 'Following', 'friends' ) . '</span> <span class="chip">example.com</span> <span class="chip">' . esc_html__( 'Edit', 'friends' ) . '</span></div>';
1357 }
1358
1359 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-chips' ) ) . '>';
1360
1361 // Role chip.
1362 $out .= '<span class="chip">' . esc_html( $author->get_role_name() ) . '</span> ';
1363
1364 // Folder chip.
1365 if ( $author instanceof Subscription ) {
1366 $folder = $author->get_folder();
1367 if ( $folder ) {
1368 $out .= '<span class="chip">&#128193; ' . esc_html( $folder->name ) . '</span> ';
1369 }
1370 }
1371
1372 // Since chip.
1373 $out .= '<span class="chip">' . esc_html( sprintf( /* translators: %s is a date */ __( 'Since %s', 'friends' ), date_i18n( __( 'F j, Y' ), strtotime( $author->user_registered ) ) ) ) . '</span> '; // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1374
1375 // User URL chip.
1376 if ( $author->user_url ) {
1377 $domain = wp_parse_url( $author->user_url, PHP_URL_HOST );
1378 if ( $domain ) {
1379 $domain = preg_replace( '/^www\./', '', $domain );
1380 $out .= '<a class="chip" href="' . esc_url( $author->user_url ) . '">' . esc_html( $domain ) . '</a> ';
1381 }
1382 }
1383
1384 // Post count chips.
1385 $post_counts = $author->get_post_count_by_post_format();
1386 foreach ( $post_counts as $post_format => $count ) {
1387 $out .= '<a class="chip" href="' . esc_url( $author->get_local_friends_page_url() . 'type/' . $post_format . '/' ) . '">' . esc_html( $friends->get_post_format_plural_string( $post_format, $count ) ) . '</a> ';
1388 }
1389
1390 // Hidden items chip.
1391 $hidden_post_count = $author->get_post_in_trash_count();
1392 if ( $hidden_post_count > 0 ) {
1393 $out .= '<span class="chip">';
1394 $out .= esc_html( sprintf( /* translators: %s is the number of hidden posts */ _n( '%s hidden items', '%s hidden items', $hidden_post_count, 'friends' ), number_format_i18n( $hidden_post_count ) ) );
1395 $out .= '</span> ';
1396 }
1397
1398 // Reaction chips.
1399 if ( class_exists( __NAMESPACE__ . '\Reactions' ) ) {
1400 foreach ( Reactions::get_available_emojis() as $slug => $reaction ) {
1401 $out .= '<a class="chip" href="' . esc_url( $author->get_local_friends_page_url() . 'reaction' . $slug . '/' ) . '">';
1402 $out .= esc_html( sprintf( /* translators: %s is an emoji */ __( 'Reacted with %s', 'friends' ), $reaction->char ) );
1403 $out .= '</a> ';
1404 }
1405 }
1406
1407 // Feed count chip.
1408 $active_feeds = count( $author->get_active_feeds() );
1409 $total_feeds = count( $author->get_feeds() );
1410 if ( $active_feeds > 0 ) {
1411 $out .= '<a class="chip" href="' . esc_url( admin_url( 'admin.php?page=edit-friend-feeds&user=' . $author->user_login ) ) . '">';
1412 $out .= esc_html( sprintf( /* translators: %s is the number of feeds */ _n( '%s feed', '%s feeds', $active_feeds, 'friends' ), $active_feeds ) );
1413 if ( $total_feeds > $active_feeds ) {
1414 $extra = $total_feeds - $active_feeds;
1415 $out .= '&nbsp;<small>(+' . esc_html( $extra ) . ' ' . esc_html__( 'more', 'friends' ) . ')</small>';
1416 }
1417 $out .= '</a> ';
1418 }
1419
1420 // Folder selector.
1421 if ( $author instanceof Subscription ) {
1422 $folders = Subscription::get_folders();
1423 $current_folder = $author->get_folder();
1424 $current_id = $current_folder ? $current_folder->term_id : 0;
1425 $nonce = wp_create_nonce( 'friends-move-to-folder' );
1426
1427 $out .= '<span class="chip friends-folder-selector">';
1428 $out .= '&#128193; <select class="friends-move-to-folder" data-id="' . esc_attr( $author->user_login ) . '" data-nonce="' . esc_attr( $nonce ) . '">';
1429 $out .= '<option value="0"' . selected( $current_id, 0, false ) . '>' . esc_html__( 'No folder', 'friends' ) . '</option>';
1430 foreach ( $folders as $folder ) {
1431 $out .= '<option value="' . esc_attr( $folder->term_id ) . '"' . selected( $current_id, $folder->term_id, false ) . '>' . esc_html( $folder->name ) . '</option>';
1432 }
1433 $out .= '<option value="new">' . esc_html__( '+ New folder', 'friends' ) . '</option>';
1434 $out .= '</select>';
1435 $out .= '</span> ';
1436 }
1437
1438 // Edit chip.
1439 $out .= '<a class="chip" href="' . esc_url( admin_url( 'admin.php?page=edit-friend&user=' . $author->user_login ) ) . '">' . esc_html__( 'Edit', 'friends' ) . '</a> ';
1440
1441 // Refresh chip.
1442 $out .= '<a class="chip" href="' . esc_url( wp_nonce_url( admin_url( 'admin.php?page=friends-refresh&user=' . $author->user_login ), 'friends-refresh' ) ) . '">' . esc_html__( 'Refresh', 'friends' ) . '</a> ';
1443
1444 $out .= '</div>';
1445 return $out;
1446 }
1447
1448 /**
1449 * Render the Friends List block
1450 *
1451 * @param array $attributes Attributes set by Blocks.
1452 * @return string The new block content.
1453 */
1454 public function render_friends_list_block( $attributes = array() ) {
1455 if ( ! isset( $attributes['user_types'] ) ) {
1456 $attributes['user_types'] = 'subscriptions';
1457 }
1458
1459 if ( 'folders' === $attributes['user_types'] ) {
1460 return $this->render_friends_list_by_folder( $attributes );
1461 }
1462
1463 if ( ! empty( $attributes['folder'] ) ) {
1464 $friends = User_Query::subscriptions_in_folder( intval( $attributes['folder'] ) );
1465 $folder_term = get_term( intval( $attributes['folder'] ), Subscription::TAXONOMY );
1466 $no_users = '';
1467 } else {
1468 $folder_term = null;
1469 switch ( $attributes['user_types'] ) {
1470 case 'starred':
1471 $friends = User_Query::starred_friends_subscriptions();
1472 $no_users = '';
1473 break;
1474 default:
1475 case 'subscriptions':
1476 $friends = User_Query::all_subscriptions();
1477 $no_users = __( "You're not following anyone yet.", 'friends' );
1478 break;
1479 }
1480 }
1481
1482 if ( $friends->get_total() === 0 ) {
1483 if ( ! $no_users ) {
1484 return '';
1485 }
1486 return '<span ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list no-users' ) ) . '>' . $no_users . '</span>';
1487 }
1488
1489 $heading = '';
1490 if ( $folder_term && ! is_wp_error( $folder_term ) ) {
1491 $heading = '<h3>&#128193; ' . esc_html( $folder_term->name ) . '</h3>';
1492 }
1493
1494 if ( ! empty( $attributes['users_inline'] ) ) {
1495 $out = $heading;
1496 $first = true;
1497 } else {
1498 $out = $heading . '<ul ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list' ) ) . '>';
1499 }
1500 $count = 0;
1501 foreach ( $friends->get_results() as $friend_user ) {
1502 ++$count;
1503 if ( ! empty( $attributes['users_inline'] ) ) {
1504 if ( ! $first ) {
1505 $out .= ', ';
1506 }
1507 $first = false;
1508 } else {
1509 $out .= '<li>';
1510 }
1511
1512 if ( Friends::has_required_privileges() ) {
1513 $url = $friend_user->get_local_friends_page_url();
1514 } else {
1515 $url = $friend_user->user_url;
1516 }
1517
1518 $out .= sprintf(
1519 '<a class="wp-user" href="%1$s">%2$s</a>',
1520 esc_url( $url ),
1521 esc_html( $friend_user->display_name )
1522 );
1523 if ( empty( $attributes['users_inline'] ) ) {
1524 $out .= '</li>';
1525 }
1526 }
1527 if ( empty( $attributes['users_inline'] ) ) {
1528 $out .= '</ul>';
1529 }
1530
1531 return $out;
1532 }
1533
1534 /**
1535 * Render the friends list grouped by folder hierarchy.
1536 *
1537 * @param array $attributes Block attributes.
1538 * @return string The rendered block HTML.
1539 */
1540 private function render_friends_list_by_folder( $attributes ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1541 $all = User_Query::all_subscriptions();
1542 $folders = Subscription::get_folders();
1543
1544 if ( $all->get_total() === 0 ) {
1545 return '<span ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list no-users' ) ) . '>' . esc_html__( "You're not following anyone yet.", 'friends' ) . '</span>';
1546 }
1547
1548 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list folders' ) ) . '>';
1549
1550 // Render each folder as a collapsible details element.
1551 foreach ( $folders as $folder ) {
1552 $folder_subs = User_Query::subscriptions_in_folder( $folder->term_id );
1553 if ( $folder_subs->get_total() === 0 ) {
1554 continue;
1555 }
1556
1557 $out .= '<details class="friends-folder" open>';
1558 $out .= '<summary>&#128193; ' . esc_html( $folder->name ) . ' <small>(' . $folder_subs->get_total() . ')</small></summary>';
1559 $out .= '<ul>';
1560 foreach ( $folder_subs->get_results() as $friend_user ) {
1561 $url = Friends::has_required_privileges() ? $friend_user->get_local_friends_page_url() : $friend_user->user_url;
1562 $out .= '<li><a class="wp-user" href="' . esc_url( $url ) . '">' . esc_html( $friend_user->display_name ? $friend_user->display_name : $friend_user->user_login ) . '</a></li>';
1563 }
1564 $out .= '</ul>';
1565 $out .= '</details>';
1566 }
1567
1568 // Render unfoldered subscriptions.
1569 $unfoldered = User_Query::unfoldered_subscriptions();
1570 if ( $unfoldered->get_total() > 0 ) {
1571 if ( ! empty( $folders ) ) {
1572 $out .= '<details class="friends-folder" open>';
1573 $out .= '<summary>' . esc_html__( 'Uncategorized', 'friends' ) . ' <small>(' . $unfoldered->get_total() . ')</small></summary>';
1574 }
1575 $out .= '<ul>';
1576 foreach ( $unfoldered->get_results() as $friend_user ) {
1577 $url = Friends::has_required_privileges() ? $friend_user->get_local_friends_page_url() : $friend_user->user_url;
1578 $out .= '<li><a class="wp-user" href="' . esc_url( $url ) . '">' . esc_html( $friend_user->display_name ? $friend_user->display_name : $friend_user->user_login ) . '</a></li>';
1579 }
1580 $out .= '</ul>';
1581 if ( ! empty( $folders ) ) {
1582 $out .= '</details>';
1583 }
1584 }
1585
1586 $out .= '</div>';
1587 return $out;
1588 }
1589
1590 /**
1591 * Render the Friend Posts block
1592 *
1593 * @param array $attributes Attributes set by Blocks.
1594 * @return string The new block content.
1595 */
1596 public function render_friend_posts_block( $attributes = array() ) {
1597 $date_formats = array(
1598 'Y m d H' => 'human',
1599 'Y m d' => 'H:i',
1600 'Y w' => 'D j, H:i',
1601 'Y' => 'M j',
1602 '' => 'M j, Y',
1603 );
1604
1605 $last_author = false;
1606
1607 $only_users = array();
1608 if ( isset( $attributes['only_users'] ) ) {
1609 $only_users = array_flip( array_filter( preg_split( '/[, ]+/', $attributes['only_users'] ) ) );
1610 }
1611 $exclude_users = array();
1612 if ( isset( $attributes['exclude_users'] ) ) {
1613 $exclude_users = array_flip( array_filter( preg_split( '/[, ]+/', $attributes['exclude_users'] ) ) );
1614 }
1615
1616 $count = max( 1, min( 100, $attributes['count'] ) );
1617 $remaining = $count;
1618 $offset = 0;
1619 $out = '<ul class="friend-posts">';
1620 while ( $remaining > 0 ) {
1621 $recent_posts = wp_get_recent_posts(
1622 array(
1623 'numberposts' => $count,
1624 'offset' => $offset,
1625 'post_type' => apply_filters( 'friends_frontend_post_types', array() ),
1626 )
1627 );
1628 if ( count( $recent_posts ) === 0 ) {
1629 break;
1630 }
1631 $offset += $count;
1632
1633 foreach ( $recent_posts as $post ) {
1634 $friend_user = new User( $post['post_author'] );
1635
1636 if ( ! empty( $only_users ) && ! isset( $only_users[ $friend_user->user_login ] ) && ! isset( $only_users[ $friend_user->ID ] ) && ! isset( $only_users[ $friend_user->display_name ] ) ) {
1637 continue;
1638 }
1639
1640 if ( ! empty( $exclude_users ) && isset( $exclude_users[ $friend_user->user_login ] ) && isset( $exclude_users[ $friend_user->ID ] ) && isset( $exclude_users[ $friend_user->display_name ] ) ) {
1641 continue;
1642 }
1643
1644 if ( $remaining <= 0 ) {
1645 break 2;
1646 }
1647 --$remaining;
1648
1649 $author = $friend_user->display_name;
1650 if ( $attributes['author_name'] || $attributes['author_avatar'] || $attributes['author_inline'] ) {
1651 if ( $attributes['author_inline'] || $last_author !== $author ) {
1652 if ( $last_author && ! $attributes['author_inline'] ) {
1653 $out .= '</li></ul></li>';
1654 }
1655 $last_author = $author;
1656
1657 $out .= '<li>';
1658 if ( $attributes['author_avatar'] ) {
1659 $out .= '<img src="' . esc_url( get_avatar_url( $post['post_author'] ) ) . '" width="20" height="20" class="avatar" />';
1660 }
1661 if ( $attributes['author_name'] ) {
1662 $out .= esc_html( $author );
1663 }
1664
1665 if ( $attributes['author_inline'] ) {
1666 $out .= ' ';
1667 } else {
1668 $out .= '<ul><li>';
1669 }
1670 } else {
1671 $out .= '<li>';
1672 }
1673 } else {
1674 $out .= '<li>';
1675 }
1676 $title = get_the_title( $post['ID'] );
1677 if ( empty( $title ) ) {
1678 $title = get_the_excerpt( $post['ID'] );
1679 }
1680 $out .= sprintf(
1681 '<a class="wp-block-friends-friend-posts" href="%1$s">%2$s</a>',
1682 esc_url( $attributes['internal_link'] ? $friend_user->get_local_friends_page_url( $post['ID'] ) : get_permalink( $post['ID'] ) ),
1683 esc_html( $title )
1684 );
1685
1686 if ( $attributes['show_date'] ) {
1687 $post_date = strtotime( $post['post_date_gmt'] );
1688 foreach ( $date_formats as $compare => $date_format ) {
1689 if ( gmdate( $compare ) === gmdate( $compare, $post_date ) ) {
1690 break;
1691 }
1692 }
1693 $out .= ' <span class="date" data-date="' . esc_attr( $post['post_date'] ) . '">';
1694 if ( 'human' === $date_format ) {
1695 /* translators: %s is a time span */
1696 $out .= sprintf( __( '%s ago' ), human_time_diff( $post_date ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1697 } else {
1698 $out .= date_i18n( $date_format, strtotime( $post['post_date'] ) );
1699 }
1700 $out .= '</span>';
1701 }
1702 $out .= '</li>';
1703 }
1704 }
1705
1706 if ( $last_author && ( $attributes['author_name'] || $attributes['author_avatar'] ) && ! $attributes['author_inline'] ) {
1707 $out .= '</ul></li>';
1708 }
1709 $out .= '</ul>';
1710
1711 return $out;
1712 }
1713
1714 /**
1715 * Enqueue the sidebar blocks editor script.
1716 */
1717 public function enqueue_sidebar_blocks() {
1718 wp_enqueue_script(
1719 'friends-sidebar-blocks',
1720 plugins_url( 'blocks/sidebar-blocks/index.js', FRIENDS_PLUGIN_FILE ),
1721 array( 'wp-blocks', 'wp-element', 'wp-server-side-render' ),
1722 Friends::VERSION,
1723 true
1724 );
1725
1726 // Pass folder data to the editor for the friends-list block.
1727 $folders = Subscription::get_folders();
1728 $folder_data = array();
1729 foreach ( $folders as $folder ) {
1730 $folder_data[] = array(
1731 'term_id' => $folder->term_id,
1732 'name' => $folder->name,
1733 );
1734 }
1735 wp_localize_script( 'friends-sidebar-blocks', 'friendsFolders', $folder_data );
1736 }
1737
1738 /**
1739 * Hide blocks that were marked as "only visible for friends".
1740 *
1741 * Since the friendship feature was removed, "only-friends" content
1742 * stays hidden to prevent accidental exposure. "not-friends" content
1743 * is shown to everyone.
1744 *
1745 * @param string $content The content provided by the user.
1746 * @param array $block Attributes for the block.
1747 * @return string The rendered content.
1748 */
1749 public function render_friends_block_visibility( $content, $block ) {
1750 $css_class = ( empty( $block['attrs'] ) || empty( $block['attrs']['className'] ) ) ? '' : $block['attrs']['className'];
1751
1752 if ( preg_match( '/\bonly-friends\b/', $css_class ) ) {
1753 // Content meant only for friends stays hidden since friendships were removed.
1754 if ( ! Friends::has_required_privileges() ) {
1755 return '';
1756 }
1757 }
1758
1759 return $content;
1760 }
1761
1762 /**
1763 * Load up the language data for the Blocks blocks
1764 */
1765 public function language_data() {
1766 $locale_data = array();
1767 if ( function_exists( 'wp_get_jed_locale_data' ) ) {
1768 $locale_data = wp_get_jed_locale_data( 'friends' );
1769 } elseif ( function_exists( 'blocks_get_jed_locale_data' ) ) {
1770 $locale_data = blocks_get_jed_locale_data( 'friends' );
1771 }
1772
1773 if ( ! empty( $locale_data ) ) {
1774 wp_add_inline_script(
1775 'friends-block-not-friends',
1776 'wp.i18n.setLocaleData( ' . wp_json_encode( $locale_data ) . ', "friends" );',
1777 'before'
1778 );
1779 }
1780 }
1781 }
1782