PluginProbe
ActivityPub / 5.1.0
ActivityPub v5.1.0
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 +273 -33 2.0.05.1.0 View file →
@@ -1,37 +1,133 @@
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 + \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() {
22 112 $data = array(
23 - 'namespace' => ACTIVITYPUB_REST_NAMESPACE,
24 - 'enabled' => array(
25 - 'site' => ! is_user_type_disabled( 'blog' ),
113 + 'namespace' => ACTIVITYPUB_REST_NAMESPACE,
114 + 'defaultAvatarUrl' => ACTIVITYPUB_PLUGIN_URL . 'assets/img/mp.jpg',
115 + 'enabled' => array(
116 + 'site' => ! is_user_type_disabled( 'blog' ),
26 117 'users' => ! is_user_type_disabled( 'user' ),
27 118 ),
28 119 );
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' );
120 +
121 + printf(
122 + "\n<script>var _activityPubOptions = %s;</script>",
123 + wp_json_encode( $data )
124 + );
32 125 }
33 126
127 + /**
128 + * Register the blocks.
129 + */
34 130 public static function register_blocks() {
35 131 \register_block_type_from_metadata(
36 132 ACTIVITYPUB_PLUGIN_DIR . '/build/followers',
37 133 array(
@@ -43,45 +139,137 @@
43 139 array(
44 140 'render_callback' => array( self::class, 'render_follow_me_block' ),
45 141 )
46 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 + );
47 156 }
48 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 + */
49 189 private static function get_user_id( $user_string ) {
50 190 if ( is_numeric( $user_string ) ) {
51 191 return absint( $user_string );
52 192 }
53 - // any other non-numeric falls back to 0, including the `site` string used in the UI
54 - return 0;
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;
55 233 }
56 234
57 235 /**
58 236 * Filter an array by a list of keys.
59 - * @param array $array The array to filter.
237 + *
238 + * @param array $data The array to filter.
60 239 * @param array $keys The keys to keep.
61 240 * @return array The filtered array.
62 241 */
63 - protected static function filter_array_by_keys( $array, $keys ) {
64 - return array_intersect_key( $array, array_flip( $keys ) );
242 + protected static function filter_array_by_keys( $data, $keys ) {
243 + return array_intersect_key( $data, array_flip( $keys ) );
65 244 }
66 245
67 246 /**
68 247 * Render the follow me block.
248 + *
69 249 * @param array $attrs The block attributes.
70 250 * @return string The HTML to render.
71 251 */
72 252 public static function render_follow_me_block( $attrs ) {
73 253 $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 - );
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 + }
80 263 }
264 +
265 + $attrs['profileData'] = self::filter_array_by_keys(
266 + $user->to_array(),
267 + array( 'icon', 'name', 'webfinger' )
268 + );
269 +
81 270 $wrapper_attributes = get_block_wrapper_attributes(
82 271 array(
83 - 'aria-label' => __( 'Follow me on the Fediverse', 'activitypub' ),
84 272 'class' => 'activitypub-follow-me-block-wrapper',
85 273 'data-attrs' => wp_json_encode( $attrs ),
86 274 )
87 275 );
@@ -88,14 +276,30 @@
88 276 // todo: render more than an empty div?
89 277 return '<div ' . $wrapper_attributes . '></div>';
90 278 }
91 279
280 + /**
281 + * Render the follower block.
282 + *
283 + * @param array $attrs The block attributes.
284 + *
285 + * @return string The HTML to render.
286 + */
92 287 public static function render_follower_block( $attrs ) {
93 288 $followee_user_id = self::get_user_id( $attrs['selectedUser'] );
94 - $per_page = absint( $attrs['per_page'] );
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'] );
95 299 $follower_data = Followers::get_followers_with_count( $followee_user_id, $per_page );
96 300
97 - $attrs['followerData']['total'] = $follower_data['total'];
301 + $attrs['followerData']['total'] = $follower_data['total'];
98 302 $attrs['followerData']['followers'] = array_map(
99 303 function ( $follower ) {
100 304 return self::filter_array_by_keys(
101 305 $follower->to_array(),
@@ -103,9 +307,9 @@
103 307 );
104 308 },
105 309 $follower_data['followers']
106 310 );
107 - $wrapper_attributes = get_block_wrapper_attributes(
311 + $wrapper_attributes = get_block_wrapper_attributes(
108 312 array(
109 313 'aria-label' => __( 'Fediverse Followers', 'activitypub' ),
110 314 'class' => 'activitypub-follower-block',
111 315 'data-attrs' => wp_json_encode( $attrs ),
@@ -124,11 +328,47 @@
124 328 $html .= '</ul></div>';
125 329 return $html;
126 330 }
127 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 + */
128 368 public static function render_follower( $follower ) {
129 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>';
130 - $template =
370 + $template =
131 371 '<a href="%s" title="%s" class="components-external-link activitypub-link" target="_blank" rel="external noreferrer noopener">
132 372 <img width="40" height="40" src="%s" class="avatar activitypub-avatar" />
133 373 <span class="activitypub-actor">
134 374 <strong class="activitypub-name">%s</strong>