PluginProbe
ActivityPub / 0.7.3
ActivityPub v0.7.3
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-activitypub.php +78 -387 2.0.00.7.3 View file →
@@ -1,17 +1,7 @@
1 1 <?php
2 2 namespace Activitypub;
3 3
4 -use Exception;
5 -use Activitypub\Signature;
6 -use Activitypub\Collection\Users;
7 -use Activitypub\Collection\Followers;
8 -
9 -use function Activitypub\sanitize_url;
10 -
11 -use function Activitypub\is_comment;
12 -use function Activitypub\is_activitypub_request;
13 -
14 4 /**
15 5 * ActivityPub Class
16 6 *
17 7 * @author Matthias Pfefferle
@@ -17,108 +7,66 @@
17 7 * @author Matthias Pfefferle
18 8 */
19 9 class Activitypub {
20 10 /**
21 - * Initialize the class, registering WordPress hooks.
11 + * Initialize the class, registering WordPress hooks
22 12 */
23 13 public static function init() {
24 - \add_filter( 'template_include', array( self::class, 'render_json_template' ), 99 );
25 - \add_action( 'template_redirect', array( self::class, 'template_redirect' ) );
26 - \add_filter( 'query_vars', array( self::class, 'add_query_vars' ) );
27 - \add_filter( 'pre_get_avatar_data', array( self::class, 'pre_get_avatar_data' ), 11, 2 );
28 - \add_filter( 'get_comment_link', array( self::class, 'remote_comment_link' ), 11, 3 );
14 + add_filter( 'template_include', array( '\Activitypub\Activitypub', 'render_json_template' ), 99 );
15 + add_filter( 'query_vars', array( '\Activitypub\Activitypub', 'add_query_vars' ) );
16 + add_action( 'init', array( '\Activitypub\Activitypub', 'add_rewrite_endpoint' ) );
17 + add_filter( 'pre_get_avatar_data', array( '\Activitypub\Activitypub', 'pre_get_avatar_data' ), 11, 2 );
29 18
30 - // Add support for ActivityPub to custom post types
31 - $post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) ? \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) ) : array();
19 + add_post_type_support( 'post', 'activitypub' );
20 + add_post_type_support( 'page', 'activitypub' );
32 21
33 - foreach ( $post_types as $post_type ) {
34 - \add_post_type_support( $post_type, 'activitypub' );
35 - }
36 -
37 - \add_action( 'wp_trash_post', array( self::class, 'trash_post' ), 1 );
38 - \add_action( 'untrash_post', array( self::class, 'untrash_post' ), 1 );
39 -
40 - \add_action( 'init', array( self::class, 'add_rewrite_rules' ), 11 );
41 -
42 - \add_action( 'after_setup_theme', array( self::class, 'theme_compat' ), 99 );
43 -
44 - \add_action( 'in_plugin_update_message-' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'plugin_update_message' ) );
45 -
46 - \add_filter( 'comment_class', array( self::class, 'comment_class' ), 10, 3 );
47 -
48 - // register several post_types
49 - self::register_post_types();
22 + add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 );
50 23 }
51 24
52 25 /**
53 - * Activation Hook
26 + * Return a AS2 JSON version of an author, post or page
54 27 *
55 - * @return void
56 - */
57 - public static function activate() {
58 - self::flush_rewrite_rules();
59 -
60 - Scheduler::register_schedules();
61 - }
62 -
63 - /**
64 - * Deactivation Hook
28 + * @param string $template the path to the template object
65 29 *
66 - * @return void
30 + * @return string the new path to the JSON template
67 31 */
68 - public static function deactivate() {
69 - self::flush_rewrite_rules();
70 - Scheduler::deregister_schedules();
71 - }
72 -
73 - /**
74 - * Uninstall Hook
75 - *
76 - * @return void
77 - */
78 - public static function uninstall() {
79 - Scheduler::deregister_schedules();
80 - }
81 -
82 - /**
83 - * Return a AS2 JSON version of an author, post or page.
84 - *
85 - * @param string $template The path to the template object.
86 - *
87 - * @return string The new path to the JSON template.
88 - */
89 32 public static function render_json_template( $template ) {
90 - if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
33 + if ( ! is_author() && ! is_singular() ) {
91 34 return $template;
92 35 }
93 36
94 - if ( ! is_activitypub_request() ) {
95 - return $template;
37 + if ( is_author() ) {
38 + $json_template = dirname( __FILE__ ) . '/../templates/json-author.php';
39 + } elseif ( is_singular() ) {
40 + $json_template = dirname( __FILE__ ) . '/../templates/json-post.php';
96 41 }
97 42
98 - $json_template = false;
43 + global $wp_query;
99 44
100 - // check if user can publish posts
101 - if ( \is_author() && is_wp_error( Users::get_by_id( \get_the_author_meta( 'ID' ) ) ) ) {
45 + if ( isset( $wp_query->query_vars['activitypub'] ) ) {
46 + return $json_template;
47 + }
48 +
49 + if ( ! isset( $_SERVER['HTTP_ACCEPT'] ) ) {
102 50 return $template;
103 51 }
104 52
105 - if ( \is_author() ) {
106 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/author-json.php';
107 - } elseif ( is_comment() ) {
108 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/comment-json.php';
109 - } elseif ( \is_singular() ) {
110 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/post-json.php';
111 - } elseif ( \is_home() ) {
112 - $json_template = ACTIVITYPUB_PLUGIN_DIR . '/templates/blog-json.php';
53 + // interpret accept header
54 + $pos = stripos( $_SERVER['HTTP_ACCEPT'], ';' );
55 + if ( $pos ) {
56 + $accept_header = substr( $_SERVER['HTTP_ACCEPT'], 0, $pos );
57 + } else {
58 + $accept_header = $_SERVER['HTTP_ACCEPT'];
113 59 }
60 + // accept header as an array
61 + $accept = explode( ',', trim( $accept_header ) );
114 62
115 - if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
116 - $verification = Signature::verify_http_signature( $_SERVER );
117 - if ( \is_wp_error( $verification ) ) {
118 - // fallback as template_loader can't return http headers
119 - return $template;
120 - }
63 + if (
64 + ! in_array( 'application/activity+json', $accept, true ) &&
65 + ! in_array( 'application/ld+json', $accept, true ) &&
66 + ! in_array( 'application/json', $accept, true )
67 + ) {
68 + return $template;
121 69 }
122 70
123 71 return $json_template;
124 72 }
@@ -123,53 +71,53 @@
123 71 return $json_template;
124 72 }
125 73
126 74 /**
127 - * Custom redirects for ActivityPub requests.
128 - *
129 - * @return void
75 + * Add the 'photos' query variable so WordPress
76 + * won't mangle it.
130 77 */
131 - public static function template_redirect() {
132 - $comment_id = get_query_var( 'c', null );
78 + public static function add_query_vars( $vars ) {
79 + $vars[] = 'activitypub';
133 80
134 - // check if it seems to be a comment
135 - if ( ! $comment_id ) {
136 - return;
137 - }
81 + return $vars;
82 + }
138 83
139 - $comment = get_comment( $comment_id );
84 + /**
85 + * Add our rewrite endpoint to permalinks and pages.
86 + */
87 + public static function add_rewrite_endpoint() {
88 + add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
89 + }
140 90
141 - // load a 404 page if `c` is set but not valid
142 - if ( ! $comment ) {
143 - global $wp_query;
144 - $wp_query->set_404();
91 + /**
92 + * Marks the post as "no webmentions sent yet"
93 + *
94 + * @param int $post_id
95 + */
96 + public static function schedule_post_activity( $new_status, $old_status, $post ) {
97 + // do not send activities if post is password protected
98 + if ( post_password_required( $post ) ) {
145 99 return;
146 100 }
147 101
148 - // stop if it's not an ActivityPub comment
149 - if ( is_activitypub_request() && $comment->user_id ) {
102 + // check if post-type supports ActivityPub
103 + $post_types = get_post_types_by_support( 'activitypub' );
104 + if ( ! in_array( $post->post_type, $post_types, true ) ) {
150 105 return;
151 106 }
152 107
153 - wp_safe_redirect( get_comment_link( $comment ) );
108 + if ( 'publish' === $new_status && 'publish' !== $old_status ) {
109 + wp_schedule_single_event( time() + wp_rand( 0, 120 ), 'activitypub_send_post_activity', array( $post->ID ) );
110 + } elseif ( 'publish' === $new_status ) {
111 + wp_schedule_single_event( time() + wp_rand( 0, 120 ), 'activitypub_send_update_activity', array( $post->ID ) );
112 + }
154 113 }
155 114
156 115 /**
157 - * Add the 'activitypub' query variable so WordPress won't mangle it.
158 - */
159 - public static function add_query_vars( $vars ) {
160 - $vars[] = 'activitypub';
161 - $vars[] = 'c';
162 - $vars[] = 'p';
163 -
164 - return $vars;
165 - }
166 -
167 - /**
168 - * Replaces the default avatar.
116 + * Replaces the default avatar
169 117 *
170 - * @param array $args Arguments passed to get_avatar_data(), after processing.
171 - * @param int|string|object $id_or_email A user ID, email address, or comment object.
118 + * @param array $args Arguments passed to get_avatar_data(), after processing.
119 + * @param int|string|object $id_or_email A user ID, email address, or comment object
172 120 *
173 121 * @return array $args
174 122 */
175 123 public static function pre_get_avatar_data( $args, $id_or_email ) {
@@ -180,31 +128,24 @@
180 128 ) {
181 129 return $args;
182 130 }
183 131
184 - $allowed_comment_types = \apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
185 - if (
186 - ! empty( $id_or_email->comment_type ) &&
187 - ! \in_array(
188 - $id_or_email->comment_type,
189 - (array) $allowed_comment_types,
190 - true
191 - )
192 - ) {
132 + $allowed_comment_types = apply_filters( 'get_avatar_comment_types', array( 'comment' ) );
133 + if ( ! empty( $id_or_email->comment_type ) && ! in_array( $id_or_email->comment_type, (array) $allowed_comment_types, true ) ) {
193 134 $args['url'] = false;
194 135 /** This filter is documented in wp-includes/link-template.php */
195 - return \apply_filters( 'get_avatar_data', $args, $id_or_email );
136 + return apply_filters( 'get_avatar_data', $args, $id_or_email );
196 137 }
197 138
198 - // Check if comment has an avatar.
139 + // check if comment has an avatar
199 140 $avatar = self::get_avatar_url( $id_or_email->comment_ID );
200 141
201 142 if ( $avatar ) {
202 - if ( ! isset( $args['class'] ) || ! \is_array( $args['class'] ) ) {
143 + if ( ! isset( $args['class'] ) || ! is_array( $args['class'] ) ) {
203 144 $args['class'] = array( 'u-photo' );
204 145 } else {
205 146 $args['class'][] = 'u-photo';
206 - $args['class'] = \array_unique( $args['class'] );
147 + $args['class'] = array_unique( $args['class'] );
207 148 }
208 149 $args['url'] = $avatar;
209 150 $args['class'][] = 'avatar-activitypub';
210 151 }
@@ -212,268 +153,18 @@
212 153 return $args;
213 154 }
214 155
215 156 /**
216 - * Function to retrieve Avatar URL if stored in meta.
157 + * Function to retrieve Avatar URL if stored in meta
217 158 *
159 + *
218 160 * @param int|WP_Comment $comment
219 161 *
220 162 * @return string $url
221 163 */
222 164 public static function get_avatar_url( $comment ) {
223 - if ( \is_numeric( $comment ) ) {
224 - $comment = \get_comment( $comment );
165 + if ( is_numeric( $comment ) ) {
166 + $comment = get_comment( $comment );
225 167 }
226 - return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
227 - }
228 -
229 - /**
230 - * Link remote comments to source url.
231 - *
232 - * @param string $comment_link
233 - * @param object|WP_Comment $comment
234 - *
235 - * @return string $url
236 - */
237 - public static function remote_comment_link( $comment_link, $comment ) {
238 - if ( ! $comment || is_admin() ) {
239 - return $comment_link;
240 - }
241 -
242 - $comment_meta = \get_comment_meta( $comment->comment_ID );
243 -
244 - if ( ! empty( $comment_meta['source_url'][0] ) ) {
245 - return $comment_meta['source_url'][0];
246 - } elseif ( ! empty( $comment_meta['source_id'][0] ) ) {
247 - return $comment_meta['source_id'][0];
248 - }
249 -
250 - return $comment_link;
251 - }
252 -
253 - /**
254 - * Store permalink in meta, to send delete Activity.
255 - *
256 - * @param string $post_id The Post ID.
257 - *
258 - * @return void
259 - */
260 - public static function trash_post( $post_id ) {
261 - \add_post_meta(
262 - $post_id,
263 - 'activitypub_canonical_url',
264 - \get_permalink( $post_id ),
265 - true
266 - );
267 - }
268 -
269 - /**
270 - * Delete permalink from meta
271 - *
272 - * @param string $post_id The Post ID
273 - *
274 - * @return void
275 - */
276 - public static function untrash_post( $post_id ) {
277 - \delete_post_meta( $post_id, 'activitypub_canonical_url' );
278 - }
279 -
280 - /**
281 - * Add rewrite rules
282 - */
283 - public static function add_rewrite_rules() {
284 - // If another system needs to take precedence over the ActivityPub rewrite rules,
285 - // they can define their own and will manually call the appropriate functions as required.
286 - if ( ACTIVITYPUB_DISABLE_REWRITES ) {
287 - return;
288 - }
289 -
290 - if ( ! \class_exists( 'Webfinger' ) ) {
291 - \add_rewrite_rule(
292 - '^.well-known/webfinger',
293 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/webfinger',
294 - 'top'
295 - );
296 - }
297 -
298 - if ( ! \class_exists( 'Nodeinfo_Endpoint' ) && true === (bool) \get_option( 'blog_public', 1 ) ) {
299 - \add_rewrite_rule(
300 - '^.well-known/nodeinfo',
301 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo/discovery',
302 - 'top'
303 - );
304 - \add_rewrite_rule(
305 - '^.well-known/x-nodeinfo2',
306 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/nodeinfo2',
307 - 'top'
308 - );
309 - }
310 -
311 - \add_rewrite_rule(
312 - '^@([\w\-\.]+)',
313 - 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/users/$matches[1]',
314 - 'top'
315 - );
316 -
317 - \add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
318 - }
319 -
320 - /**
321 - * Flush rewrite rules;
322 - */
323 - public static function flush_rewrite_rules() {
324 - self::add_rewrite_rules();
325 - \flush_rewrite_rules();
326 - }
327 -
328 - /**
329 - * Theme compatibility stuff
330 - *
331 - * @return void
332 - */
333 - public static function theme_compat() {
334 - $site_icon = get_theme_support( 'custom-logo' );
335 -
336 - if ( ! $site_icon ) {
337 - // custom logo support
338 - add_theme_support(
339 - 'custom-logo',
340 - array(
341 - 'height' => 80,
342 - 'width' => 80,
343 - )
344 - );
345 - }
346 -
347 - $custom_header = get_theme_support( 'custom-header' );
348 -
349 - if ( ! $custom_header ) {
350 - // This theme supports a custom header
351 - $custom_header_args = array(
352 - 'width' => 1250,
353 - 'height' => 600,
354 - 'header-text' => true,
355 - );
356 - add_theme_support( 'custom-header', $custom_header_args );
357 - }
358 - }
359 -
360 - /**
361 - * Display plugin upgrade notice to users
362 - *
363 - * @param array $data The plugin data
364 - *
365 - * @return void
366 - */
367 - public static function plugin_update_message( $data ) {
368 - if ( ! isset( $data['upgrade_notice'] ) ) {
369 - return;
370 - }
371 -
372 - printf(
373 - '<div class="update-message">%s</div>',
374 - wp_kses(
375 - wpautop( $data['upgrade_notice '] ),
376 - array(
377 - 'p' => array(),
378 - 'a' => array( 'href', 'title' ),
379 - 'strong' => array(),
380 - 'em' => array(),
381 - )
382 - )
383 - );
384 - }
385 -
386 - /**
387 - * Register the "Followers" Taxonomy
388 - *
389 - * @return void
390 - */
391 - private static function register_post_types() {
392 - \register_post_type(
393 - Followers::POST_TYPE,
394 - array(
395 - 'labels' => array(
396 - 'name' => _x( 'Followers', 'post_type plural name', 'activitypub' ),
397 - 'singular_name' => _x( 'Follower', 'post_type single name', 'activitypub' ),
398 - ),
399 - 'public' => false,
400 - 'hierarchical' => false,
401 - 'rewrite' => false,
402 - 'query_var' => false,
403 - 'delete_with_user' => false,
404 - 'can_export' => true,
405 - 'supports' => array(),
406 - )
407 - );
408 -
409 - \register_post_meta(
410 - Followers::POST_TYPE,
411 - 'activitypub_inbox',
412 - array(
413 - 'type' => 'string',
414 - 'single' => true,
415 - 'sanitize_callback' => 'sanitize_url',
416 - )
417 - );
418 -
419 - \register_post_meta(
420 - Followers::POST_TYPE,
421 - 'activitypub_errors',
422 - array(
423 - 'type' => 'string',
424 - 'single' => false,
425 - 'sanitize_callback' => function ( $value ) {
426 - if ( ! is_string( $value ) ) {
427 - throw new Exception( 'Error message is no valid string' );
428 - }
429 -
430 - return esc_sql( $value );
431 - },
432 - )
433 - );
434 -
435 - \register_post_meta(
436 - Followers::POST_TYPE,
437 - 'activitypub_user_id',
438 - array(
439 - 'type' => 'string',
440 - 'single' => false,
441 - 'sanitize_callback' => function ( $value ) {
442 - return esc_sql( $value );
443 - },
444 - )
445 - );
446 -
447 - \register_post_meta(
448 - Followers::POST_TYPE,
449 - 'activitypub_actor_json',
450 - array(
451 - 'type' => 'string',
452 - 'single' => true,
453 - 'sanitize_callback' => function ( $value ) {
454 - return sanitize_text_field( $value );
455 - },
456 - )
457 - );
458 -
459 - \do_action( 'activitypub_after_register_post_type' );
460 - }
461 -
462 - /**
463 - * Filters the CSS classes to add an ActivityPub class.
464 - *
465 - * @param string[] $classes An array of comment classes.
466 - * @param string[] $css_class An array of additional classes added to the list.
467 - * @param string $comment_id The comment ID as a numeric string.
468 - *
469 - * @return string[] An array of classes.
470 - */
471 - public static function comment_class( $classes, $css_class, $comment_id ) {
472 - // check if ActivityPub comment
473 - if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
474 - $classes[] = 'activitypub-comment';
475 - }
476 -
477 - return $classes;
168 + return get_comment_meta( $comment->comment_ID, 'avatar_url', true );
478 169 }
479 170 }