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