PluginProbe
Friends / trunk
Friends vtrunk
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 2.9.2 All 87 releases
friends / includes / class-blocks.php

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

1,783 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 $content .= Link_Preview::render();
1070
1071 return '<div class="wp-block-friends-post-content">' . $content . '</div>';
1072 }
1073
1074 /**
1075 * Render the friends/post-permalink block.
1076 *
1077 * Shows "X ago on domain.com Y min read" for each post.
1078 *
1079 * @param array $attributes Block attributes.
1080 * @return string The rendered block HTML.
1081 */
1082 public function render_post_permalink_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1083 global $post;
1084 if ( ! $post ) {
1085 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>';
1086 }
1087
1088 $friend_user = User::get_post_author( $post );
1089 if ( ! $friend_user || is_wp_error( $friend_user ) ) {
1090 return '';
1091 }
1092
1093 $author_url = $friend_user->get_local_friends_page_url();
1094 $guid = get_the_guid( $post );
1095 $domain = wp_parse_url( $guid, PHP_URL_HOST );
1096 $local_url = $author_url . $post->ID . '/';
1097
1098 // Reading time.
1099 $read_time_seconds = Frontend::calculate_read_time( get_the_content() );
1100 $read_time = '';
1101 if ( $read_time_seconds >= 60 ) {
1102 $mins = ceil( $read_time_seconds / MINUTE_IN_SECONDS );
1103 /* translators: Time difference between two dates, in minutes (min=minute). %s: Number of minutes. */
1104 $read_time = sprintf( _n( '%s min', '%s mins', $mins ), $mins ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1105 } elseif ( $read_time_seconds > 20 ) {
1106 $read_time = _x( '< 1 min', 'reading time', 'friends' );
1107 }
1108
1109 /* translators: %s is a time span */
1110 $time_ago = sprintf( __( '%s ago' ), human_time_diff( get_post_time( 'U', true ) ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1111 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-post-permalink' ) ) . '>';
1112 $out .= sprintf(
1113 // translators: %1$s is a date or relative time, %2$s is a site name or domain.
1114 _x( '%1$s on %2$s', 'at-date-on-post', 'friends' ),
1115 '<a href="' . esc_url( $local_url ) . '" title="' . esc_attr( get_the_time( 'r' ) ) . '">' . esc_html( $time_ago ) . '</a>',
1116 '<a href="' . esc_url( $guid ) . '" rel="noopener noreferrer" target="_blank">' . esc_html( $domain ) . '</a>'
1117 );
1118 if ( $read_time ) {
1119 $out .= ' <span class="reading-time" title="' . esc_attr__( 'Estimated reading time', 'friends' ) . '">';
1120 $out .= esc_html( sprintf( /* translators: %s is a timeframe */ __( '%s read', 'friends' ), $read_time ) );
1121 $out .= '</span>';
1122 }
1123 $out .= '</div>';
1124
1125 return $out;
1126 }
1127
1128 /**
1129 * Render the friends/post-reblog block.
1130 *
1131 * @return string The rendered block HTML.
1132 */
1133 public function render_post_reblog_block() {
1134 global $post;
1135 if ( ! $post ) {
1136 return '<span class="wp-block-friends-post-reblog">' . esc_html_x( 'Reblog', 'button', 'friends' ) . '</span>';
1137 }
1138
1139 $friend_user = User::get_post_author( $post );
1140 if ( $friend_user && ! is_wp_error( $friend_user ) && get_current_user_id() === $friend_user->ID ) {
1141 return '';
1142 }
1143
1144 $post_id = get_the_ID();
1145 $reblog_nonce = wp_create_nonce( 'friends-reblog' );
1146 $reblog_status = get_post_meta( $post_id, 'reblogged', true ) ? ' dashicons-saved' : '';
1147
1148 $out = '<span class="wp-block-friends-post-reblog">';
1149 $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' ) . '">';
1150 $out .= '<i class="dashicons dashicons-controls-repeat"></i> <span class="text">' . esc_html_x( 'Reblog', 'button', 'friends' ) . '</span>';
1151 $out .= '<i class="friends-reblog-status dashicons' . esc_attr( $reblog_status ) . '"></i>';
1152 $out .= '</a></span>';
1153
1154 return $out;
1155 }
1156
1157 /**
1158 * Render the friends/post-boost block.
1159 *
1160 * @return string The rendered block HTML.
1161 */
1162 public function render_post_boost_block() {
1163 global $post;
1164 if ( ! $post ) {
1165 return '<span class="wp-block-friends-post-boost">' . esc_html_x( 'Boost', 'button', 'friends' ) . '</span>';
1166 }
1167
1168 if ( ! has_filter( 'friends_boost' ) && ! class_exists( '\Activitypub\Activitypub' ) ) {
1169 return '';
1170 }
1171
1172 $friend_user = User::get_post_author( $post );
1173 if ( $friend_user && ! is_wp_error( $friend_user ) && get_current_user_id() === $friend_user->ID ) {
1174 return '';
1175 }
1176
1177 $post_id = get_the_ID();
1178 $boost_nonce = wp_create_nonce( 'friends-boost' );
1179 $boost_status = get_post_meta( $post_id, 'boosted', true ) ? ' dashicons-saved' : '';
1180
1181 $out = '<span class="wp-block-friends-post-boost">';
1182 $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' ) . '">';
1183 $out .= '<i class="dashicons dashicons-controls-repeat"></i> <span class="text">' . esc_html_x( 'Boost', 'button', 'friends' ) . '</span>';
1184 $out .= '<i class="friends-boost-status dashicons' . esc_attr( $boost_status ) . '"></i>';
1185 $out .= '</a></span>';
1186
1187 return $out;
1188 }
1189
1190 /**
1191 * Render the friends/post-reactions block.
1192 *
1193 * @return string The rendered block HTML.
1194 */
1195 public function render_post_reactions_block() {
1196 global $post;
1197 if ( ! $post ) {
1198 return '<span class="wp-block-friends-post-reactions">⭐ ❤️</span>';
1199 }
1200
1201 if ( ! class_exists( __NAMESPACE__ . '\Reactions' ) ) {
1202 return '';
1203 }
1204
1205 $post_id = get_the_ID();
1206 $reactions = Reactions::get_post_reactions();
1207 $reaction_nonce = wp_create_nonce( 'friends-reaction' );
1208
1209 $out = '<span class="wp-block-friends-post-reactions">';
1210
1211 foreach ( $reactions as $slug => $reaction ) {
1212 $pressed = $reaction->user_reacted ? ' pressed' : '';
1213 $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 ) . '">';
1214 $out .= '<span>' . esc_html( $reaction->emoji ) . '</span> ' . esc_html( $reaction->count );
1215 $out .= '</button> ';
1216 }
1217
1218 // Render available emojis as inline buttons for reactions not yet used.
1219 $available = Reactions::get_available_emojis();
1220 foreach ( $available as $slug => $emoji ) {
1221 if ( isset( $reactions[ $slug ] ) ) {
1222 continue;
1223 }
1224 $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> ';
1225 }
1226
1227 $out .= '</span>';
1228 return $out;
1229 }
1230
1231 /**
1232 * Render the friends/post-comments block.
1233 *
1234 * @return string The rendered block HTML.
1235 */
1236 public function render_post_comments_block() {
1237 global $post;
1238 if ( ! $post ) {
1239 return '<span class="wp-block-friends-post-comments">💬 ' . esc_html__( 'Comments' ) . '</span>'; // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1240 }
1241
1242 $post_id = get_the_ID();
1243 $has_mention = get_post_meta( $post_id, '_has_mention_in_comments', true );
1244 $comment_icon = $has_mention ? 'format-status' : 'admin-comments';
1245 $comment_text = $has_mention ? __( 'Comments (You were mentioned)', 'friends' ) : __( 'Comments' ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1246
1247 $out = '<div class="wp-block-friends-post-comments">';
1248 $out .= '<a href="#" class="comments" data-id="' . esc_attr( $post_id ) . '" data-cnonce="' . esc_attr( wp_create_nonce( 'comments-' . $post_id ) ) . '">';
1249 $out .= '<i class="dashicons dashicons-' . esc_attr( $comment_icon ) . '"></i> <span class="text">' . esc_html( $comment_text ) . '</span>';
1250 $out .= '</a>';
1251 $out .= '<div class="comments-content closed"></div>';
1252 $out .= '</div>';
1253
1254 return $out;
1255 }
1256
1257 /**
1258 * Get the current author from the frontend context.
1259 *
1260 * @return User|null
1261 */
1262 private function get_frontend_author() {
1263 $friends = Friends::get_instance();
1264 return $friends->frontend->author;
1265 }
1266
1267 /**
1268 * Render the friends/author-star block.
1269 *
1270 * @return string The rendered block HTML.
1271 */
1272 public function render_author_star_block() {
1273 $author = $this->get_frontend_author();
1274 if ( ! $author ) {
1275 return '<span class="wp-block-friends-author-star">&#9734;</span>';
1276 }
1277
1278 $starred = $author->is_starred();
1279 $star_class = $starred ? 'dashicons-star-filled starred' : 'dashicons-star-empty not-starred';
1280 $star_nonce = wp_create_nonce( 'star-' . $author->user_login );
1281
1282 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>';
1283 }
1284
1285 /**
1286 * Render the friends/author-avatar block.
1287 *
1288 * @return string The rendered block HTML.
1289 */
1290 public function render_author_avatar_block() {
1291 $author = $this->get_frontend_author();
1292 if ( ! $author ) {
1293 return '<span class="wp-block-friends-author-avatar"></span>';
1294 }
1295
1296 $avatar_url = $author->get_avatar_url();
1297 if ( ! $avatar_url ) {
1298 return '<span class="wp-block-friends-author-avatar"></span>';
1299 }
1300
1301 return '<img class="wp-block-friends-author-avatar" src="' . esc_url( $avatar_url ) . '" width="36" height="36" />';
1302 }
1303
1304 /**
1305 * Render the friends/author-name block.
1306 *
1307 * @param array $attributes Block attributes.
1308 * @return string The rendered block HTML.
1309 */
1310 public function render_author_name_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1311 $author = $this->get_frontend_author();
1312 if ( ! $author ) {
1313 return '<h2 ' . $this->get_wrapper_attributes(
1314 array(
1315 'class' => 'wp-block-friends-author-name',
1316 'id' => 'page-title',
1317 )
1318 ) . '>' . esc_html__( 'Author Name', 'friends' ) . '</h2>';
1319 }
1320
1321 return '<h2 ' . $this->get_wrapper_attributes(
1322 array(
1323 'class' => 'wp-block-friends-author-name',
1324 'id' => 'page-title',
1325 )
1326 ) . '>' . wp_kses_post( apply_filters( 'friends_author_display_name_html', esc_html( $author->display_name ), $author->display_name, $author ) ) . '</h2>';
1327 }
1328
1329 /**
1330 * Render the friends/author-description block.
1331 *
1332 * @param array $attributes Block attributes.
1333 * @return string The rendered block HTML.
1334 */
1335 public function render_author_description_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1336 $author = $this->get_frontend_author();
1337 if ( ! $author ) {
1338 return '<p ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-description' ) ) . '><em>' . esc_html__( 'Author description will appear here.', 'friends' ) . '</em></p>';
1339 }
1340 if ( ! $author->description ) {
1341 return '';
1342 }
1343
1344 return '<p ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-description' ) ) . '>' . wp_kses( $author->description, array( 'a' => array( 'href' => array() ) ) ) . '</p>';
1345 }
1346
1347 /**
1348 * Render the friends/author-chips block.
1349 *
1350 * @param array $attributes Block attributes.
1351 * @return string The rendered block HTML.
1352 */
1353 public function render_author_chips_block( $attributes = array() ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1354 $friends = Friends::get_instance();
1355 $author = $this->get_frontend_author();
1356 if ( ! $author ) {
1357 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>';
1358 }
1359
1360 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-author-chips' ) ) . '>';
1361
1362 // Role chip.
1363 $out .= '<span class="chip">' . esc_html( $author->get_role_name() ) . '</span> ';
1364
1365 // Folder chip.
1366 if ( $author instanceof Subscription ) {
1367 $folder = $author->get_folder();
1368 if ( $folder ) {
1369 $out .= '<span class="chip">&#128193; ' . esc_html( $folder->name ) . '</span> ';
1370 }
1371 }
1372
1373 // Since chip.
1374 $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
1375
1376 // User URL chip.
1377 if ( $author->user_url ) {
1378 $domain = wp_parse_url( $author->user_url, PHP_URL_HOST );
1379 if ( $domain ) {
1380 $domain = preg_replace( '/^www\./', '', $domain );
1381 $out .= '<a class="chip" href="' . esc_url( $author->user_url ) . '">' . esc_html( $domain ) . '</a> ';
1382 }
1383 }
1384
1385 // Post count chips.
1386 $post_counts = $author->get_post_count_by_post_format();
1387 foreach ( $post_counts as $post_format => $count ) {
1388 $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> ';
1389 }
1390
1391 // Hidden items chip.
1392 $hidden_post_count = $author->get_post_in_trash_count();
1393 if ( $hidden_post_count > 0 ) {
1394 $out .= '<span class="chip">';
1395 $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 ) ) );
1396 $out .= '</span> ';
1397 }
1398
1399 // Reaction chips.
1400 if ( class_exists( __NAMESPACE__ . '\Reactions' ) ) {
1401 foreach ( Reactions::get_available_emojis() as $slug => $reaction ) {
1402 $out .= '<a class="chip" href="' . esc_url( $author->get_local_friends_page_url() . 'reaction' . $slug . '/' ) . '">';
1403 $out .= esc_html( sprintf( /* translators: %s is an emoji */ __( 'Reacted with %s', 'friends' ), $reaction->char ) );
1404 $out .= '</a> ';
1405 }
1406 }
1407
1408 // Feed count chip.
1409 $active_feeds = count( $author->get_active_feeds() );
1410 $total_feeds = count( $author->get_feeds() );
1411 if ( $active_feeds > 0 ) {
1412 $out .= '<a class="chip" href="' . esc_url( admin_url( 'admin.php?page=edit-friend-feeds&user=' . $author->user_login ) ) . '">';
1413 $out .= esc_html( sprintf( /* translators: %s is the number of feeds */ _n( '%s feed', '%s feeds', $active_feeds, 'friends' ), $active_feeds ) );
1414 if ( $total_feeds > $active_feeds ) {
1415 $extra = $total_feeds - $active_feeds;
1416 $out .= '&nbsp;<small>(+' . esc_html( $extra ) . ' ' . esc_html__( 'more', 'friends' ) . ')</small>';
1417 }
1418 $out .= '</a> ';
1419 }
1420
1421 // Folder selector.
1422 if ( $author instanceof Subscription ) {
1423 $folders = Subscription::get_folders();
1424 $current_folder = $author->get_folder();
1425 $current_id = $current_folder ? $current_folder->term_id : 0;
1426 $nonce = wp_create_nonce( 'friends-move-to-folder' );
1427
1428 $out .= '<span class="chip friends-folder-selector">';
1429 $out .= '&#128193; <select class="friends-move-to-folder" data-id="' . esc_attr( $author->user_login ) . '" data-nonce="' . esc_attr( $nonce ) . '">';
1430 $out .= '<option value="0"' . selected( $current_id, 0, false ) . '>' . esc_html__( 'No folder', 'friends' ) . '</option>';
1431 foreach ( $folders as $folder ) {
1432 $out .= '<option value="' . esc_attr( $folder->term_id ) . '"' . selected( $current_id, $folder->term_id, false ) . '>' . esc_html( $folder->name ) . '</option>';
1433 }
1434 $out .= '<option value="new">' . esc_html__( '+ New folder', 'friends' ) . '</option>';
1435 $out .= '</select>';
1436 $out .= '</span> ';
1437 }
1438
1439 // Edit chip.
1440 $out .= '<a class="chip" href="' . esc_url( admin_url( 'admin.php?page=edit-friend&user=' . $author->user_login ) ) . '">' . esc_html__( 'Edit', 'friends' ) . '</a> ';
1441
1442 // Refresh chip.
1443 $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> ';
1444
1445 $out .= '</div>';
1446 return $out;
1447 }
1448
1449 /**
1450 * Render the Friends List block
1451 *
1452 * @param array $attributes Attributes set by Blocks.
1453 * @return string The new block content.
1454 */
1455 public function render_friends_list_block( $attributes = array() ) {
1456 if ( ! isset( $attributes['user_types'] ) ) {
1457 $attributes['user_types'] = 'subscriptions';
1458 }
1459
1460 if ( 'folders' === $attributes['user_types'] ) {
1461 return $this->render_friends_list_by_folder( $attributes );
1462 }
1463
1464 if ( ! empty( $attributes['folder'] ) ) {
1465 $friends = User_Query::subscriptions_in_folder( intval( $attributes['folder'] ) );
1466 $folder_term = get_term( intval( $attributes['folder'] ), Subscription::TAXONOMY );
1467 $no_users = '';
1468 } else {
1469 $folder_term = null;
1470 switch ( $attributes['user_types'] ) {
1471 case 'starred':
1472 $friends = User_Query::starred_friends_subscriptions();
1473 $no_users = '';
1474 break;
1475 default:
1476 case 'subscriptions':
1477 $friends = User_Query::all_subscriptions();
1478 $no_users = __( "You're not following anyone yet.", 'friends' );
1479 break;
1480 }
1481 }
1482
1483 if ( $friends->get_total() === 0 ) {
1484 if ( ! $no_users ) {
1485 return '';
1486 }
1487 return '<span ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list no-users' ) ) . '>' . $no_users . '</span>';
1488 }
1489
1490 $heading = '';
1491 if ( $folder_term && ! is_wp_error( $folder_term ) ) {
1492 $heading = '<h3>&#128193; ' . esc_html( $folder_term->name ) . '</h3>';
1493 }
1494
1495 if ( ! empty( $attributes['users_inline'] ) ) {
1496 $out = $heading;
1497 $first = true;
1498 } else {
1499 $out = $heading . '<ul ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list' ) ) . '>';
1500 }
1501 $count = 0;
1502 foreach ( $friends->get_results() as $friend_user ) {
1503 ++$count;
1504 if ( ! empty( $attributes['users_inline'] ) ) {
1505 if ( ! $first ) {
1506 $out .= ', ';
1507 }
1508 $first = false;
1509 } else {
1510 $out .= '<li>';
1511 }
1512
1513 if ( Friends::has_required_privileges() ) {
1514 $url = $friend_user->get_local_friends_page_url();
1515 } else {
1516 $url = $friend_user->user_url;
1517 }
1518
1519 $out .= sprintf(
1520 '<a class="wp-user" href="%1$s">%2$s</a>',
1521 esc_url( $url ),
1522 esc_html( $friend_user->display_name )
1523 );
1524 if ( empty( $attributes['users_inline'] ) ) {
1525 $out .= '</li>';
1526 }
1527 }
1528 if ( empty( $attributes['users_inline'] ) ) {
1529 $out .= '</ul>';
1530 }
1531
1532 return $out;
1533 }
1534
1535 /**
1536 * Render the friends list grouped by folder hierarchy.
1537 *
1538 * @param array $attributes Block attributes.
1539 * @return string The rendered block HTML.
1540 */
1541 private function render_friends_list_by_folder( $attributes ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
1542 $all = User_Query::all_subscriptions();
1543 $folders = Subscription::get_folders();
1544
1545 if ( $all->get_total() === 0 ) {
1546 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>';
1547 }
1548
1549 $out = '<div ' . $this->get_wrapper_attributes( array( 'class' => 'wp-block-friends-friends-list folders' ) ) . '>';
1550
1551 // Render each folder as a collapsible details element.
1552 foreach ( $folders as $folder ) {
1553 $folder_subs = User_Query::subscriptions_in_folder( $folder->term_id );
1554 if ( $folder_subs->get_total() === 0 ) {
1555 continue;
1556 }
1557
1558 $out .= '<details class="friends-folder" open>';
1559 $out .= '<summary>&#128193; ' . esc_html( $folder->name ) . ' <small>(' . $folder_subs->get_total() . ')</small></summary>';
1560 $out .= '<ul>';
1561 foreach ( $folder_subs->get_results() as $friend_user ) {
1562 $url = Friends::has_required_privileges() ? $friend_user->get_local_friends_page_url() : $friend_user->user_url;
1563 $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>';
1564 }
1565 $out .= '</ul>';
1566 $out .= '</details>';
1567 }
1568
1569 // Render unfoldered subscriptions.
1570 $unfoldered = User_Query::unfoldered_subscriptions();
1571 if ( $unfoldered->get_total() > 0 ) {
1572 if ( ! empty( $folders ) ) {
1573 $out .= '<details class="friends-folder" open>';
1574 $out .= '<summary>' . esc_html__( 'Uncategorized', 'friends' ) . ' <small>(' . $unfoldered->get_total() . ')</small></summary>';
1575 }
1576 $out .= '<ul>';
1577 foreach ( $unfoldered->get_results() as $friend_user ) {
1578 $url = Friends::has_required_privileges() ? $friend_user->get_local_friends_page_url() : $friend_user->user_url;
1579 $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>';
1580 }
1581 $out .= '</ul>';
1582 if ( ! empty( $folders ) ) {
1583 $out .= '</details>';
1584 }
1585 }
1586
1587 $out .= '</div>';
1588 return $out;
1589 }
1590
1591 /**
1592 * Render the Friend Posts block
1593 *
1594 * @param array $attributes Attributes set by Blocks.
1595 * @return string The new block content.
1596 */
1597 public function render_friend_posts_block( $attributes = array() ) {
1598 $date_formats = array(
1599 'Y m d H' => 'human',
1600 'Y m d' => 'H:i',
1601 'Y w' => 'D j, H:i',
1602 'Y' => 'M j',
1603 '' => 'M j, Y',
1604 );
1605
1606 $last_author = false;
1607
1608 $only_users = array();
1609 if ( isset( $attributes['only_users'] ) ) {
1610 $only_users = array_flip( array_filter( preg_split( '/[, ]+/', $attributes['only_users'] ) ) );
1611 }
1612 $exclude_users = array();
1613 if ( isset( $attributes['exclude_users'] ) ) {
1614 $exclude_users = array_flip( array_filter( preg_split( '/[, ]+/', $attributes['exclude_users'] ) ) );
1615 }
1616
1617 $count = max( 1, min( 100, $attributes['count'] ) );
1618 $remaining = $count;
1619 $offset = 0;
1620 $out = '<ul class="friend-posts">';
1621 while ( $remaining > 0 ) {
1622 $recent_posts = wp_get_recent_posts(
1623 array(
1624 'numberposts' => $count,
1625 'offset' => $offset,
1626 'post_type' => apply_filters( 'friends_frontend_post_types', array() ),
1627 )
1628 );
1629 if ( count( $recent_posts ) === 0 ) {
1630 break;
1631 }
1632 $offset += $count;
1633
1634 foreach ( $recent_posts as $post ) {
1635 $friend_user = new User( $post['post_author'] );
1636
1637 if ( ! empty( $only_users ) && ! isset( $only_users[ $friend_user->user_login ] ) && ! isset( $only_users[ $friend_user->ID ] ) && ! isset( $only_users[ $friend_user->display_name ] ) ) {
1638 continue;
1639 }
1640
1641 if ( ! empty( $exclude_users ) && isset( $exclude_users[ $friend_user->user_login ] ) && isset( $exclude_users[ $friend_user->ID ] ) && isset( $exclude_users[ $friend_user->display_name ] ) ) {
1642 continue;
1643 }
1644
1645 if ( $remaining <= 0 ) {
1646 break 2;
1647 }
1648 --$remaining;
1649
1650 $author = $friend_user->display_name;
1651 if ( $attributes['author_name'] || $attributes['author_avatar'] || $attributes['author_inline'] ) {
1652 if ( $attributes['author_inline'] || $last_author !== $author ) {
1653 if ( $last_author && ! $attributes['author_inline'] ) {
1654 $out .= '</li></ul></li>';
1655 }
1656 $last_author = $author;
1657
1658 $out .= '<li>';
1659 if ( $attributes['author_avatar'] ) {
1660 $out .= '<img src="' . esc_url( get_avatar_url( $post['post_author'] ) ) . '" width="20" height="20" class="avatar" />';
1661 }
1662 if ( $attributes['author_name'] ) {
1663 $out .= esc_html( $author );
1664 }
1665
1666 if ( $attributes['author_inline'] ) {
1667 $out .= ' ';
1668 } else {
1669 $out .= '<ul><li>';
1670 }
1671 } else {
1672 $out .= '<li>';
1673 }
1674 } else {
1675 $out .= '<li>';
1676 }
1677 $title = get_the_title( $post['ID'] );
1678 if ( empty( $title ) ) {
1679 $title = get_the_excerpt( $post['ID'] );
1680 }
1681 $out .= sprintf(
1682 '<a class="wp-block-friends-friend-posts" href="%1$s">%2$s</a>',
1683 esc_url( $attributes['internal_link'] ? $friend_user->get_local_friends_page_url( $post['ID'] ) : get_permalink( $post['ID'] ) ),
1684 esc_html( $title )
1685 );
1686
1687 if ( $attributes['show_date'] ) {
1688 $post_date = strtotime( $post['post_date_gmt'] );
1689 foreach ( $date_formats as $compare => $date_format ) {
1690 if ( gmdate( $compare ) === gmdate( $compare, $post_date ) ) {
1691 break;
1692 }
1693 }
1694 $out .= ' <span class="date" data-date="' . esc_attr( $post['post_date'] ) . '">';
1695 if ( 'human' === $date_format ) {
1696 /* translators: %s is a time span */
1697 $out .= sprintf( __( '%s ago' ), human_time_diff( $post_date ) ); // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
1698 } else {
1699 $out .= date_i18n( $date_format, strtotime( $post['post_date'] ) );
1700 }
1701 $out .= '</span>';
1702 }
1703 $out .= '</li>';
1704 }
1705 }
1706
1707 if ( $last_author && ( $attributes['author_name'] || $attributes['author_avatar'] ) && ! $attributes['author_inline'] ) {
1708 $out .= '</ul></li>';
1709 }
1710 $out .= '</ul>';
1711
1712 return $out;
1713 }
1714
1715 /**
1716 * Enqueue the sidebar blocks editor script.
1717 */
1718 public function enqueue_sidebar_blocks() {
1719 wp_enqueue_script(
1720 'friends-sidebar-blocks',
1721 plugins_url( 'blocks/sidebar-blocks/index.js', FRIENDS_PLUGIN_FILE ),
1722 array( 'wp-blocks', 'wp-element', 'wp-server-side-render' ),
1723 Friends::VERSION,
1724 true
1725 );
1726
1727 // Pass folder data to the editor for the friends-list block.
1728 $folders = Subscription::get_folders();
1729 $folder_data = array();
1730 foreach ( $folders as $folder ) {
1731 $folder_data[] = array(
1732 'term_id' => $folder->term_id,
1733 'name' => $folder->name,
1734 );
1735 }
1736 wp_localize_script( 'friends-sidebar-blocks', 'friendsFolders', $folder_data );
1737 }
1738
1739 /**
1740 * Hide blocks that were marked as "only visible for friends".
1741 *
1742 * Since the friendship feature was removed, "only-friends" content
1743 * stays hidden to prevent accidental exposure. "not-friends" content
1744 * is shown to everyone.
1745 *
1746 * @param string $content The content provided by the user.
1747 * @param array $block Attributes for the block.
1748 * @return string The rendered content.
1749 */
1750 public function render_friends_block_visibility( $content, $block ) {
1751 $css_class = ( empty( $block['attrs'] ) || empty( $block['attrs']['className'] ) ) ? '' : $block['attrs']['className'];
1752
1753 if ( preg_match( '/\bonly-friends\b/', $css_class ) ) {
1754 // Content meant only for friends stays hidden since friendships were removed.
1755 if ( ! Friends::has_required_privileges() ) {
1756 return '';
1757 }
1758 }
1759
1760 return $content;
1761 }
1762
1763 /**
1764 * Load up the language data for the Blocks blocks
1765 */
1766 public function language_data() {
1767 $locale_data = array();
1768 if ( function_exists( 'wp_get_jed_locale_data' ) ) {
1769 $locale_data = wp_get_jed_locale_data( 'friends' );
1770 } elseif ( function_exists( 'blocks_get_jed_locale_data' ) ) {
1771 $locale_data = blocks_get_jed_locale_data( 'friends' );
1772 }
1773
1774 if ( ! empty( $locale_data ) ) {
1775 wp_add_inline_script(
1776 'friends-block-not-friends',
1777 'wp.i18n.setLocaleData( ' . wp_json_encode( $locale_data ) . ', "friends" );',
1778 'before'
1779 );
1780 }
1781 }
1782 }
1783