PluginProbe
ActivityPub / 7.8.2
ActivityPub v7.8.2
9.3.1 9.3.0 9.2.2 9.2.1 9.2.0 9.1.0 9.0.2 9.0.1 9.0.0 8.3.0 8.2.1 8.2.0 8.1.1 1.0.5 1.0.6 1.0.7 1.0.8 1.0.9 1.1.0 1.2.0 1.3.0 2.0.0 2.0.1 2.1.0 2.1.1 All 160 releases
activitypub / build / reactions / render.php

render.php in ActivityPub 7.8.2, at build/reactions/render.php

257 lines 6.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Server-side rendering of the `activitypub/reactions` block.
4 *
5 * @package ActivityPub
6 */
7
8 use Activitypub\Blocks;
9 use Activitypub\Comment;
10
11 use function Activitypub\is_activitypub_request;
12
13 if ( is_activitypub_request() || is_feed() ) {
14 return;
15 }
16
17 // Get the default display style based on WordPress avatar settings.
18 $default_display_style = get_option( 'show_avatars', true ) ? 'facepile' : 'compact';
19
20 /* @var array $attributes Block attributes. */
21 $attributes = wp_parse_args(
22 $attributes,
23 array(
24 'align' => null,
25 'displayStyle' => $default_display_style,
26 )
27 );
28
29 /* @var \WP_Block $block Current block. */
30 $block = $block ?? '';
31
32 /* @var string $content Block content. */
33 $content = $content ?? '';
34
35 if ( empty( $content ) ) {
36 // Fallback for v1.0.0 blocks.
37 $_title = $attributes['title'] ?? __( 'Fediverse Reactions', 'activitypub' );
38 $content = '<h6 class="wp-block-heading">' . esc_html( $_title ) . '</h6>';
39 unset( $attributes['title'] );
40 } else {
41 $content = implode( PHP_EOL, wp_list_pluck( $block->parsed_block['innerBlocks'], 'innerHTML' ) );
42 // Hide empty headings.
43 if ( empty( wp_strip_all_tags( $content ) ) ) {
44 $content = '';
45 }
46 }
47
48 // Get the Post ID from attributes or use the current post.
49 $_post_id = $attributes['postId'] ?? get_the_ID();
50
51 // Generate a unique ID for the block.
52 $block_id = 'activitypub-reactions-block-' . wp_unique_id();
53
54 /*
55 * Determine display style - compact style hides avatars.
56 * For auto-hooked blocks without explicit style, use avatar setting to determine style.
57 */
58 $has_style_class = isset( $attributes['className'] ) && strpos( $attributes['className'], 'is-style-' ) !== false;
59 if ( ! $has_style_class ) {
60 $attributes['className'] = trim( ( $attributes['className'] ?? '' ) . ' is-style-' . $default_display_style );
61 $attributes['displayStyle'] = $default_display_style;
62 }
63
64 $show_avatars = 'facepile' === $attributes['displayStyle'];
65
66 // Fetch reactions.
67 $reactions = array();
68
69 foreach ( Comment::get_comment_types() as $_type => $type_object ) {
70 $_comments = get_comments(
71 array(
72 'post_id' => $_post_id,
73 'type' => $_type,
74 'status' => 'approve',
75 'parent' => 0,
76 )
77 );
78
79 if ( empty( $_comments ) ) {
80 continue;
81 }
82
83 $count = count( $_comments );
84 // phpcs:disable WordPress.WP.I18n
85 $label = sprintf(
86 _n(
87 $type_object['count_single'],
88 $type_object['count_plural'],
89 $count,
90 'activitypub'
91 ),
92 number_format_i18n( $count )
93 );
94 // phpcs:enable WordPress.WP.I18n
95
96 $reactions[ $_type ] = array(
97 'label' => $label,
98 'count' => $count,
99 'items' => array_map(
100 static function ( $comment ) {
101 return array(
102 'name' => html_entity_decode( $comment->comment_author ),
103 'url' => $comment->comment_author_url,
104 'avatar' => get_avatar_url( $comment ),
105 );
106 },
107 $_comments
108 ),
109 );
110 }
111
112 if ( empty( $reactions ) ) {
113 echo '<!-- Reactions block: No reactions found. -->';
114 return;
115 }
116
117 // Set up the Interactivity API config.
118 wp_interactivity_config(
119 'activitypub/reactions',
120 array(
121 'defaultAvatarUrl' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
122 'namespace' => ACTIVITYPUB_REST_NAMESPACE,
123 )
124 );
125
126 // Set up the Interactivity API state.
127 wp_interactivity_state( 'activitypub/reactions', array( 'reactions' => array( $_post_id => $reactions ) ) );
128
129 // Render a subset of the most recent reactions for facepile.
130 $reactions = array_map(
131 static function ( $reaction ) use ( $attributes ) {
132 $count = 20;
133 if ( 'wide' === $attributes['align'] ) {
134 $count = 40;
135 } elseif ( 'full' === $attributes['align'] ) {
136 $count = 60;
137 }
138
139 $reaction['items'] = array_slice( array_reverse( $reaction['items'] ), 0, $count );
140
141 return $reaction;
142 },
143 $reactions
144 );
145
146 // Initialize the context for the block.
147 $context = array(
148 'blockId' => $block_id,
149 'modal' => array(
150 'isCompact' => true,
151 'isOpen' => false,
152 'items' => array(),
153 ),
154 'postId' => $_post_id,
155 'reactions' => $reactions,
156 );
157
158 // Build reactions content.
159 ob_start();
160 ?>
161 <div class="activitypub-reactions">
162 <?php
163 foreach ( $reactions as $_type => $reaction ) :
164 /* translators: %s: reaction type. */
165 $aria_label = sprintf( __( 'View all %s', 'activitypub' ), Comment::get_comment_type_attr( $_type, 'label' ) );
166 ?>
167 <div class="reaction-group" data-reaction-type="<?php echo esc_attr( $_type ); ?>">
168 <?php if ( $show_avatars ) : ?>
169 <ul class="reaction-avatars">
170 <template data-wp-each="context.reactions.<?php echo esc_attr( $_type ); ?>.items">
171 <li>
172 <a
173 data-wp-bind--href="context.item.url"
174 data-wp-bind--title="context.item.name"
175 target="_blank"
176 rel="noopener noreferrer"
177 >
178 <img
179 data-wp-bind--src="context.item.avatar"
180 data-wp-bind--alt="context.item.name"
181 data-wp-on--error="callbacks.setDefaultAvatar"
182 class="reaction-avatar"
183 height="32"
184 width="32"
185 src=""
186 alt=""
187 />
188 </a>
189 </li>
190 </template>
191 </ul>
192 <?php endif; ?>
193 <button
194 class="reaction-label wp-element-button"
195 data-reaction-type="<?php echo esc_attr( $_type ); ?>"
196 data-wp-on--click="actions.toggleModal"
197 aria-label="<?php echo esc_attr( $aria_label ); ?>"
198 >
199 <?php echo esc_html( $reaction['label'] ); ?>
200 </button>
201 </div>
202 <?php endforeach; ?>
203 </div>
204 <?php
205 $reactions_content = ob_get_clean();
206
207 // Build modal content.
208 ob_start();
209 ?>
210 <ul class="reactions-list">
211 <template data-wp-each="context.modal.items">
212 <li class="reaction-item">
213 <a data-wp-bind--href="context.item.url" target="_blank" rel="noopener noreferrer">
214 <?php if ( $show_avatars ) : ?>
215 <img
216 alt=""
217 data-wp-bind--alt="context.item.name"
218 data-wp-bind--src="context.item.avatar"
219 data-wp-on--error="callbacks.setDefaultAvatar"
220 src=""
221 />
222 <?php endif; ?>
223 <span class="reaction-name" data-wp-text="context.item.name"></span>
224 </a>
225 </li>
226 </template>
227 </ul>
228 <?php
229 $modal_content = ob_get_clean();
230
231 // Combine reactions and modal.
232 ob_start();
233 Blocks::render_modal(
234 array(
235 'is_compact' => true,
236 'content' => $modal_content,
237 )
238 );
239 $inner_content = $reactions_content . ob_get_clean();
240
241 $wrapper_attributes = get_block_wrapper_attributes(
242 array(
243 'id' => $block_id,
244 'class' => $attributes['className'] ?? '',
245 'data-wp-interactive' => 'activitypub/reactions',
246 'data-wp-context' => wp_json_encode( $context, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP ),
247 'data-wp-init' => 'callbacks.initReactions',
248 )
249 );
250
251 // Render the block with common wrapper.
252 ?>
253 <div <?php echo $wrapper_attributes; // phpcs:ignore WordPress.Security.EscapeOutput ?>>
254 <?php echo $content; // phpcs:ignore WordPress.Security.EscapeOutput ?>
255 <?php echo $inner_content; // phpcs:ignore WordPress.Security.EscapeOutput ?>
256 </div>
257