PluginProbe
ActivityPub / 1.3.0
ActivityPub v1.3.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
activitypub / includes / class-activitypub.php

class-activitypub.php in ActivityPub 1.3.0, at includes/class-activitypub.php

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