PluginProbe
ActivityPub / 4.7.1
ActivityPub v4.7.1
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 / includes / class-blocks.php

class-blocks.php in ActivityPub 4.7.1, at includes/class-blocks.php

394 lines 11.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blocks file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Collection\Followers;
11 use Activitypub\Collection\Actors;
12
13 /**
14 * Block class.
15 */
16 class Blocks {
17 /**
18 * Initialize the class, registering WordPress hooks.
19 */
20 public static function init() {
21 // This is already being called on the init hook, so just add it.
22 self::register_blocks();
23
24 \add_action( 'wp_footer', array( self::class, 'inject_activitypub_options' ) );
25 \add_action( 'admin_print_scripts', array( self::class, 'inject_activitypub_options' ) );
26 \add_action( 'load-post-new.php', array( self::class, 'handle_in_reply_to_get_param' ) );
27 // Add editor plugin.
28 \add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_editor_assets' ) );
29 \add_action( 'init', array( self::class, 'register_postmeta' ), 11 );
30 }
31
32 /**
33 * Register post meta for content warnings.
34 */
35 public static function register_postmeta() {
36 $ap_post_types = \get_post_types_by_support( 'activitypub' );
37 foreach ( $ap_post_types as $post_type ) {
38 \register_post_meta(
39 $post_type,
40 'activitypub_content_warning',
41 array(
42 'show_in_rest' => true,
43 'single' => true,
44 'type' => 'string',
45 'sanitize_callback' => function ( $warning ) {
46 if ( $warning ) {
47 return \sanitize_text_field( $warning );
48 }
49
50 return null;
51 },
52 )
53 );
54 \register_post_meta(
55 $post_type,
56 'activitypub_content_visibility',
57 array(
58 'show_in_rest' => true,
59 'single' => true,
60 'type' => 'string',
61 'sanitize_callback' => function ( $visibility ) {
62 $options = array(
63 ACTIVITYPUB_CONTENT_VISIBILITY_QUIET_PUBLIC,
64 ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL,
65 );
66
67 if ( in_array( $visibility, $options, true ) ) {
68 return $visibility;
69 }
70
71 return null;
72 },
73 )
74 );
75 }
76 }
77
78 /**
79 * Enqueue the block editor assets.
80 */
81 public static function enqueue_editor_assets() {
82 // Check for our supported post types.
83 $current_screen = \get_current_screen();
84 $ap_post_types = \get_post_types_by_support( 'activitypub' );
85 if ( ! $current_screen || ! in_array( $current_screen->post_type, $ap_post_types, true ) ) {
86 return;
87 }
88 $asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/editor-plugin/plugin.asset.php';
89 $plugin_url = plugins_url( 'build/editor-plugin/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
90 wp_enqueue_script( 'activitypub-block-editor', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
91 }
92
93 /**
94 * Enqueue the reply handle script if the in_reply_to GET param is set.
95 */
96 public static function handle_in_reply_to_get_param() {
97 // Only load the script if the in_reply_to GET param is set, action happens there, not here.
98 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
99 if ( ! isset( $_GET['in_reply_to'] ) ) {
100 return;
101 }
102
103 $asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/reply-intent/plugin.asset.php';
104 $plugin_url = plugins_url( 'build/reply-intent/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
105 wp_enqueue_script( 'activitypub-reply-intent', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
106 }
107
108 /**
109 * Output ActivityPub options as a script tag.
110 */
111 public static function inject_activitypub_options() {
112 $data = array(
113 'namespace' => ACTIVITYPUB_REST_NAMESPACE,
114 'defaultAvatarUrl' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
115 'enabled' => array(
116 'site' => ! is_user_type_disabled( 'blog' ),
117 'users' => ! is_user_type_disabled( 'user' ),
118 ),
119 );
120
121 printf(
122 "\n<script>var _activityPubOptions = %s;</script>",
123 wp_json_encode( $data )
124 );
125 }
126
127 /**
128 * Register the blocks.
129 */
130 public static function register_blocks() {
131 \register_block_type_from_metadata(
132 ACTIVITYPUB_PLUGIN_DIR . '/build/followers',
133 array(
134 'render_callback' => array( self::class, 'render_follower_block' ),
135 )
136 );
137 \register_block_type_from_metadata(
138 ACTIVITYPUB_PLUGIN_DIR . '/build/follow-me',
139 array(
140 'render_callback' => array( self::class, 'render_follow_me_block' ),
141 )
142 );
143 \register_block_type_from_metadata(
144 ACTIVITYPUB_PLUGIN_DIR . '/build/reply',
145 array(
146 'render_callback' => array( self::class, 'render_reply_block' ),
147 )
148 );
149
150 \register_block_type_from_metadata(
151 ACTIVITYPUB_PLUGIN_DIR . '/build/reactions',
152 array(
153 'render_callback' => array( self::class, 'render_post_reactions_block' ),
154 )
155 );
156 }
157
158 /**
159 * Render the post reactions block.
160 *
161 * @param array $attrs The block attributes.
162 *
163 * @return string The HTML to render.
164 */
165 public static function render_post_reactions_block( $attrs ) {
166 if ( ! isset( $attrs['postId'] ) ) {
167 $attrs['postId'] = get_the_ID();
168 }
169
170 $wrapper_attributes = get_block_wrapper_attributes(
171 array(
172 'class' => 'activitypub-reactions-block',
173 'data-attrs' => wp_json_encode( $attrs ),
174 )
175 );
176
177 return sprintf(
178 '<div %s></div>',
179 $wrapper_attributes
180 );
181 }
182
183 /**
184 * Get the user ID from a user string.
185 *
186 * @param string $user_string The user string. Can be a user ID, 'site', or 'inherit'.
187 * @return int|null The user ID, or null if the 'inherit' string is not supported in this context.
188 */
189 private static function get_user_id( $user_string ) {
190 if ( is_numeric( $user_string ) ) {
191 return absint( $user_string );
192 }
193
194 // If the user string is 'site', return the Blog User ID.
195 if ( 'site' === $user_string ) {
196 return Actors::BLOG_USER_ID;
197 }
198
199 // The only other value should be 'inherit', which means to use the query context to determine the User.
200 if ( 'inherit' !== $user_string ) {
201 return null;
202 }
203
204 // For a homepage/front page, if the Blog User is active, use it.
205 if ( ( is_front_page() || is_home() ) && ! is_user_type_disabled( 'blog' ) ) {
206 return Actors::BLOG_USER_ID;
207 }
208
209 // If we're in a loop, use the post author.
210 $author_id = get_the_author_meta( 'ID' );
211 if ( $author_id ) {
212 return $author_id;
213 }
214
215 // For other pages, the queried object will clue us in.
216 $queried_object = get_queried_object();
217 if ( ! $queried_object ) {
218 return null;
219 }
220
221 // If we're on a user archive page, use that user's ID.
222 if ( is_a( $queried_object, 'WP_User' ) ) {
223 return $queried_object->ID;
224 }
225
226 // For a single post, use the post author's ID.
227 if ( is_a( $queried_object, 'WP_Post' ) ) {
228 return get_the_author_meta( 'ID' );
229 }
230
231 // We won't properly account for some conditions, like tag archives.
232 return null;
233 }
234
235 /**
236 * Filter an array by a list of keys.
237 *
238 * @param array $data The array to filter.
239 * @param array $keys The keys to keep.
240 * @return array The filtered array.
241 */
242 protected static function filter_array_by_keys( $data, $keys ) {
243 return array_intersect_key( $data, array_flip( $keys ) );
244 }
245
246 /**
247 * Render the follow me block.
248 *
249 * @param array $attrs The block attributes.
250 * @return string The HTML to render.
251 */
252 public static function render_follow_me_block( $attrs ) {
253 $user_id = self::get_user_id( $attrs['selectedUser'] );
254 $user = Actors::get_by_id( $user_id );
255 if ( is_wp_error( $user ) ) {
256 if ( 'inherit' === $attrs['selectedUser'] ) {
257 // If the user is 'inherit' and we couldn't determine the user, don't render anything.
258 return '<!-- Follow Me block: `inherit` mode does not display on this type of page -->';
259 } else {
260 // If the user is a specific ID and we couldn't find it, render an error message.
261 return '<!-- Follow Me block: user not found -->';
262 }
263 }
264
265 $attrs['profileData'] = self::filter_array_by_keys(
266 $user->to_array(),
267 array( 'icon', 'name', 'webfinger' )
268 );
269
270 $wrapper_attributes = get_block_wrapper_attributes(
271 array(
272 'class' => 'activitypub-follow-me-block-wrapper',
273 'data-attrs' => wp_json_encode( $attrs ),
274 )
275 );
276 // todo: render more than an empty div?
277 return '<div ' . $wrapper_attributes . '></div>';
278 }
279
280 /**
281 * Render the follower block.
282 *
283 * @param array $attrs The block attributes.
284 *
285 * @return string The HTML to render.
286 */
287 public static function render_follower_block( $attrs ) {
288 $followee_user_id = self::get_user_id( $attrs['selectedUser'] );
289 if ( is_null( $followee_user_id ) ) {
290 return '<!-- Followers block: `inherit` mode does not display on this type of page -->';
291 }
292
293 $user = Actors::get_by_id( $followee_user_id );
294 if ( is_wp_error( $user ) ) {
295 return '<!-- Followers block: `' . $followee_user_id . '` not an active ActivityPub user -->';
296 }
297
298 $per_page = absint( $attrs['per_page'] );
299 $follower_data = Followers::get_followers_with_count( $followee_user_id, $per_page );
300
301 $attrs['followerData']['total'] = $follower_data['total'];
302 $attrs['followerData']['followers'] = array_map(
303 function ( $follower ) {
304 return self::filter_array_by_keys(
305 $follower->to_array(),
306 array( 'icon', 'name', 'preferredUsername', 'url' )
307 );
308 },
309 $follower_data['followers']
310 );
311 $wrapper_attributes = get_block_wrapper_attributes(
312 array(
313 'aria-label' => __( 'Fediverse Followers', 'activitypub' ),
314 'class' => 'activitypub-follower-block',
315 'data-attrs' => wp_json_encode( $attrs ),
316 )
317 );
318
319 $html = '<div ' . $wrapper_attributes . '>';
320 if ( $attrs['title'] ) {
321 $html .= '<h3>' . esc_html( $attrs['title'] ) . '</h3>';
322 }
323 $html .= '<ul>';
324 foreach ( $follower_data['followers'] as $follower ) {
325 $html .= '<li>' . self::render_follower( $follower ) . '</li>';
326 }
327 // We are only pagination on the JS side. Could be revisited but we gotta ship!
328 $html .= '</ul></div>';
329 return $html;
330 }
331
332 /**
333 * Render the reply block.
334 *
335 * @param array $attrs The block attributes.
336 *
337 * @return string The HTML to render.
338 */
339 public static function render_reply_block( $attrs ) {
340 $html = '';
341
342 if ( ! empty( $attrs['url'] ) ) {
343 $html = sprintf(
344 '<p><a title="%2$s" aria-label="%2$s" href="%1$s" class="u-in-reply-to" target="_blank">%3$s</a></p>',
345 esc_url( $attrs['url'] ),
346 esc_attr__( 'This post is a response to the referenced content.', 'activitypub' ),
347 // translators: %s is the URL of the post being replied to.
348 sprintf( __( '&#8620;%s', 'activitypub' ), \str_replace( array( 'https://', 'http://' ), '', esc_url( $attrs['url'] ) ) )
349 );
350 }
351
352 /**
353 * Filter the reply block.
354 *
355 * @param string $html The HTML to render.
356 * @param array $attrs The block attributes.
357 */
358 return apply_filters( 'activitypub_reply_block', $html, $attrs );
359 }
360
361 /**
362 * Render a follower.
363 *
364 * @param \Activitypub\Model\Follower $follower The follower to render.
365 *
366 * @return string The HTML to render.
367 */
368 public static function render_follower( $follower ) {
369 $external_svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" class="components-external-link__icon css-rvs7bx esh4a730" aria-hidden="true" focusable="false"><path d="M18.2 17c0 .7-.6 1.2-1.2 1.2H7c-.7 0-1.2-.6-1.2-1.2V7c0-.7.6-1.2 1.2-1.2h3.2V4.2H7C5.5 4.2 4.2 5.5 4.2 7v10c0 1.5 1.2 2.8 2.8 2.8h10c1.5 0 2.8-1.2 2.8-2.8v-3.6h-1.5V17zM14.9 3v1.5h3.7l-6.4 6.4 1.1 1.1 6.4-6.4v3.7h1.5V3h-6.3z"></path></svg>';
370 $template =
371 '<a href="%s" title="%s" class="components-external-link activitypub-link" target="_blank" rel="external noreferrer noopener">
372 <img width="40" height="40" src="%s" class="avatar activitypub-avatar" />
373 <span class="activitypub-actor">
374 <strong class="activitypub-name">%s</strong>
375 <span class="sep">/</span>
376 <span class="activitypub-handle">@%s</span>
377 </span>
378 %s
379 </a>';
380
381 $data = $follower->to_array();
382
383 return sprintf(
384 $template,
385 esc_url( object_to_uri( $data['url'] ) ),
386 esc_attr( $data['name'] ),
387 esc_attr( $data['icon']['url'] ),
388 esc_html( $data['name'] ),
389 esc_html( $data['preferredUsername'] ),
390 $external_svg
391 );
392 }
393 }
394