PluginProbe
ActivityPub / trunk
ActivityPub vtrunk
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-options.php

class-options.php in ActivityPub trunk, at includes/class-options.php

1,046 lines 29.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Options file.
4 *
5 * @package Activitypub
6 */
7
8 namespace Activitypub;
9
10 use Activitypub\Model\Blog;
11
12 /**
13 * Options class.
14 */
15 class Options {
16
17 /**
18 * Initialize the options.
19 */
20 public static function init() {
21 \add_action( 'admin_init', array( self::class, 'register_settings' ) );
22 \add_action( 'rest_api_init', array( self::class, 'register_settings' ) );
23
24 \add_filter( 'pre_option_activitypub_actor_mode', array( self::class, 'pre_option_activitypub_actor_mode' ) );
25 \add_filter( 'pre_option_activitypub_authorized_fetch', array( self::class, 'pre_option_activitypub_authorized_fetch' ) );
26 \add_filter( 'pre_option_activitypub_vary_header', array( self::class, 'pre_option_activitypub_vary_header' ) );
27 \add_filter( 'pre_option_activitypub_following_ui', array( self::class, 'pre_option_activitypub_following_ui' ) );
28 \add_filter( 'pre_option_activitypub_create_posts', array( self::class, 'pre_option_activitypub_create_posts' ) );
29
30 \add_filter( 'pre_option_activitypub_distribution_mode', array( self::class, 'pre_option_activitypub_distribution_mode' ) );
31 \add_filter( 'activitypub_dispatcher_batch_size', array( self::class, 'filter_dispatcher_batch_size' ) );
32 \add_filter( 'activitypub_scheduler_async_batch_pause', array( self::class, 'filter_scheduler_batch_pause' ), 10, 2 );
33
34 \add_filter( 'pre_option_activitypub_allow_likes', array( self::class, 'maybe_disable_interactions' ) );
35 \add_filter( 'pre_option_activitypub_allow_replies', array( self::class, 'maybe_disable_interactions' ) );
36
37 \add_filter( 'default_option_activitypub_negotiate_content', array( self::class, 'default_option_activitypub_negotiate_content' ) );
38 \add_filter( 'option_activitypub_max_image_attachments', array( self::class, 'default_max_image_attachments' ) );
39 \add_filter( 'option_activitypub_support_post_types', array( self::class, 'support_post_types_ensure_array' ) );
40 \add_filter( 'option_activitypub_object_type', array( self::class, 'default_object_type' ) );
41
42 \add_filter( 'option_activitypub_outbox_purge_days', array( self::class, 'sanitize_purge_days' ) );
43 \add_filter( 'option_activitypub_inbox_purge_days', array( self::class, 'sanitize_purge_days' ) );
44 \add_filter( 'option_activitypub_ap_post_purge_days', array( self::class, 'sanitize_purge_days' ) );
45
46 \add_action( 'update_option_activitypub_relay_mode', array( self::class, 'relay_mode_changed' ), 10, 2 );
47 }
48
49 /**
50 * Register ActivityPub settings.
51 */
52 public static function register_settings() {
53 /*
54 * Options Group: activitypub
55 */
56 \register_setting(
57 'activitypub',
58 'activitypub_post_content_type',
59 array(
60 'type' => 'string',
61 'description' => 'Use title and link, summary, full or custom content',
62 'show_in_rest' => array(
63 'schema' => array(
64 'enum' => array( 'title', 'excerpt', 'content' ),
65 ),
66 ),
67 'default' => 'content',
68 )
69 );
70
71 \register_setting(
72 'activitypub',
73 'activitypub_custom_post_content',
74 array(
75 'type' => 'string',
76 'description' => 'Define your own custom post template',
77 'show_in_rest' => true,
78 'default' => ACTIVITYPUB_CUSTOM_POST_CONTENT,
79 )
80 );
81
82 \register_setting(
83 'activitypub',
84 'activitypub_max_image_attachments',
85 array(
86 'type' => 'integer',
87 'description' => 'Number of images to attach to posts.',
88 'default' => ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS,
89 'sanitize_callback' => static function ( $value ) {
90 return \is_numeric( $value ) ? \absint( $value ) : ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS;
91 },
92 )
93 );
94
95 \register_setting(
96 'activitypub',
97 'activitypub_use_hashtags',
98 array(
99 'type' => 'boolean',
100 'description' => 'Add hashtags in the content as native tags and replace the #tag with the tag-link',
101 'default' => '0',
102 )
103 );
104
105 \register_setting(
106 'activitypub',
107 'activitypub_use_opengraph',
108 array(
109 'type' => 'boolean',
110 'description' => 'Automatically add "fediverse:creator" OpenGraph tags for Authors and the Blog-User.',
111 'default' => '1',
112 )
113 );
114
115 \register_setting(
116 'activitypub',
117 'activitypub_support_post_types',
118 array(
119 'type' => 'string',
120 'description' => 'Enable ActivityPub support for post types',
121 'show_in_rest' => true,
122 'default' => array( 'post' ),
123 )
124 );
125
126 \register_setting(
127 'activitypub',
128 'activitypub_actor_mode',
129 array(
130 'type' => 'string',
131 'description' => 'Choose your preferred Actor-Mode.',
132 'default' => ACTIVITYPUB_ACTOR_MODE,
133 'show_in_rest' => array(
134 'schema' => array(
135 'type' => 'string',
136 'enum' => array(
137 ACTIVITYPUB_ACTOR_MODE,
138 ACTIVITYPUB_BLOG_MODE,
139 ACTIVITYPUB_ACTOR_AND_BLOG_MODE,
140 ),
141 ),
142 ),
143 )
144 );
145
146 \register_setting(
147 'activitypub',
148 'activitypub_attribution_domains',
149 array(
150 'type' => 'string',
151 'description' => 'Websites allowed to credit you.',
152 'default' => home_host(),
153 'sanitize_callback' => array( Sanitize::class, 'host_list' ),
154 )
155 );
156
157 \register_setting(
158 'activitypub',
159 'activitypub_allow_likes',
160 array(
161 'type' => 'integer',
162 'description' => 'Allow likes.',
163 'default' => '1',
164 'sanitize_callback' => 'absint',
165 )
166 );
167
168 \register_setting(
169 'activitypub',
170 'activitypub_allow_reposts',
171 array(
172 'type' => 'integer',
173 'description' => 'Allow reposts.',
174 'default' => '1',
175 'sanitize_callback' => 'absint',
176 )
177 );
178
179 \register_setting(
180 'activitypub',
181 'activitypub_auto_approve_reactions',
182 array(
183 'type' => 'integer',
184 'description' => 'Auto-approve Reactions.',
185 'default' => '0',
186 'sanitize_callback' => 'absint',
187 )
188 );
189
190 \register_setting(
191 'activitypub',
192 'activitypub_default_quote_policy',
193 array(
194 'type' => 'string',
195 'description' => 'Default quote policy for new posts.',
196 'default' => ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
197 'sanitize_callback' => static function ( $value ) {
198 $allowed = array(
199 ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
200 ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS,
201 ACTIVITYPUB_INTERACTION_POLICY_ME,
202 );
203 return \in_array( $value, $allowed, true ) ? $value : ACTIVITYPUB_INTERACTION_POLICY_ANYONE;
204 },
205 )
206 );
207
208 \register_setting(
209 'activitypub',
210 'activitypub_default_feature_policy',
211 array(
212 'type' => 'string',
213 'description' => 'Default policy for who can include this site\'s actors in featured collections (FEP-7aa9).',
214 'default' => ACTIVITYPUB_INTERACTION_POLICY_ME,
215 'sanitize_callback' => static function ( $value ) {
216 $allowed = array(
217 ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
218 ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS,
219 ACTIVITYPUB_INTERACTION_POLICY_ME,
220 );
221 return \in_array( $value, $allowed, true ) ? $value : ACTIVITYPUB_INTERACTION_POLICY_ME;
222 },
223 )
224 );
225
226 \register_setting(
227 'activitypub',
228 'activitypub_relays',
229 array(
230 'type' => 'array',
231 'description' => 'Relays',
232 'default' => array(),
233 'sanitize_callback' => array( Sanitize::class, 'url_list' ),
234 )
235 );
236
237 \register_setting(
238 'activitypub',
239 'activitypub_site_blocked_actors',
240 array(
241 'type' => 'array',
242 'description' => 'Site-wide blocked ActivityPub actors.',
243 'default' => array(),
244 'sanitize_callback' => array( Sanitize::class, 'identifier_list' ),
245 )
246 );
247
248 /*
249 * Options Group: activitypub_advanced
250 */
251 \register_setting(
252 'activitypub_advanced',
253 'activitypub_outbox_purge_days',
254 array(
255 'type' => 'integer',
256 'description' => 'Number of days to keep items in the Outbox.',
257 'default' => ACTIVITYPUB_OUTBOX_PURGE_DAYS,
258 'sanitize_callback' => static function ( $value ) {
259 return \max( 1, \absint( $value ) );
260 },
261 )
262 );
263
264 \register_setting(
265 'activitypub_advanced',
266 'activitypub_inbox_purge_days',
267 array(
268 'type' => 'integer',
269 'description' => 'Number of days to keep items in the Inbox.',
270 'default' => ACTIVITYPUB_INBOX_PURGE_DAYS,
271 'sanitize_callback' => static function ( $value ) {
272 return \max( 1, \absint( $value ) );
273 },
274 )
275 );
276
277 \register_setting(
278 'activitypub_advanced',
279 'activitypub_ap_post_purge_days',
280 array(
281 'type' => 'integer',
282 'description' => 'Number of days to keep remote posts.',
283 'default' => ACTIVITYPUB_AP_POST_PURGE_DAYS,
284 'sanitize_callback' => static function ( $value ) {
285 return \max( 1, \absint( $value ) );
286 },
287 )
288 );
289
290 \register_setting(
291 'activitypub_advanced',
292 'activitypub_vary_header',
293 array(
294 'type' => 'boolean',
295 'description' => 'Add the Vary header to the ActivityPub response.',
296 'default' => true,
297 )
298 );
299
300 \register_setting(
301 'activitypub_advanced',
302 'activitypub_content_negotiation',
303 array(
304 'type' => 'boolean',
305 'description' => 'Enable content negotiation.',
306 'default' => true,
307 )
308 );
309
310 \register_setting(
311 'activitypub_advanced',
312 'activitypub_authorized_fetch',
313 array(
314 'type' => 'boolean',
315 'description' => 'Require HTTP signature authentication.',
316 'default' => false,
317 )
318 );
319
320 \register_setting(
321 'activitypub_advanced',
322 'activitypub_rfc9421_signature',
323 array(
324 'type' => 'boolean',
325 'description' => 'Use RFC-9421 signature.',
326 'default' => true,
327 )
328 );
329
330 \register_setting(
331 'activitypub_advanced',
332 'activitypub_following_ui',
333 array(
334 'type' => 'boolean',
335 'description' => 'Show Following UI in admin menus and settings.',
336 'default' => false,
337 )
338 );
339
340 \register_setting(
341 'activitypub_advanced',
342 'activitypub_reader_ui',
343 array(
344 'type' => 'boolean',
345 'description' => 'Enable the Reader to view posts from accounts you follow.',
346 'default' => false,
347 )
348 );
349
350 \register_setting(
351 'activitypub_advanced',
352 'activitypub_create_posts',
353 array(
354 'type' => 'boolean',
355 'description' => 'Allow creating posts via ActivityPub.',
356 'default' => false,
357 )
358 );
359
360 \register_setting(
361 'activitypub_advanced',
362 'activitypub_api',
363 array(
364 'type' => 'boolean',
365 'description' => 'Enable the ActivityPub API to allow third-party clients.',
366 'default' => false,
367 )
368 );
369
370 \register_setting(
371 'activitypub_advanced',
372 'activitypub_object_type',
373 array(
374 'type' => 'string',
375 'description' => 'The Activity-Object-Type',
376 'show_in_rest' => array(
377 'schema' => array(
378 'enum' => array( 'note', 'wordpress-post-format' ),
379 ),
380 ),
381 'default' => ACTIVITYPUB_DEFAULT_OBJECT_TYPE,
382 )
383 );
384
385 \register_setting(
386 'activitypub_advanced',
387 'activitypub_relay_mode',
388 array(
389 'type' => 'integer',
390 'description' => 'Enable relay mode to forward public activities to all followers.',
391 'default' => 0,
392 'sanitize_callback' => 'absint',
393 )
394 );
395
396 $default_distribution = self::get_distribution_preset_values()['default'];
397
398 \register_setting(
399 'activitypub_advanced',
400 'activitypub_distribution_mode',
401 array(
402 'type' => 'string',
403 'description' => \__( 'Distribution mode for federation delivery.', 'activitypub' ),
404 'default' => 'default',
405 'sanitize_callback' => array( self::class, 'sanitize_distribution_mode' ),
406 )
407 );
408
409 \register_setting(
410 'activitypub_advanced',
411 'activitypub_custom_batch_size',
412 array(
413 'type' => 'integer',
414 'description' => \__( 'Custom batch size for federation delivery.', 'activitypub' ),
415 'default' => $default_distribution['batch_size'],
416 'sanitize_callback' => static function ( $value ) {
417 return \min( 500, \max( 1, \absint( $value ) ) );
418 },
419 )
420 );
421
422 \register_setting(
423 'activitypub_advanced',
424 'activitypub_custom_batch_pause',
425 array(
426 'type' => 'integer',
427 'description' => \__( 'Custom pause in seconds between batches.', 'activitypub' ),
428 'default' => $default_distribution['pause'],
429 'sanitize_callback' => static function ( $value ) {
430 return \min( 3600, \absint( $value ) );
431 },
432 )
433 );
434
435 /*
436 * Options Group: activitypub_blog
437 */
438 \register_setting(
439 'activitypub_blog',
440 'activitypub_blog_description',
441 array(
442 'type' => 'string',
443 'description' => 'The Description of the Blog-User',
444 'show_in_rest' => true,
445 'default' => '',
446 )
447 );
448
449 \register_setting(
450 'activitypub_blog',
451 'activitypub_blog_identifier',
452 array(
453 'type' => 'string',
454 'description' => 'The Identifier of the Blog-User',
455 'show_in_rest' => true,
456 'default' => Blog::get_default_username(),
457 'sanitize_callback' => array( Sanitize::class, 'blog_identifier' ),
458 )
459 );
460
461 \register_setting(
462 'activitypub_blog',
463 'activitypub_header_image',
464 array(
465 'type' => 'integer',
466 'description' => 'The Attachment-ID of the Sites Header-Image',
467 'default' => null,
468 )
469 );
470
471 \register_setting(
472 'activitypub_blog',
473 'activitypub_blog_user_mailer_new_dm',
474 array(
475 'type' => 'integer',
476 'description' => 'Send a notification when someone sends a user of the blog a direct message.',
477 'default' => 1,
478 )
479 );
480
481 \register_setting(
482 'activitypub_blog',
483 'activitypub_blog_user_mailer_new_follower',
484 array(
485 'type' => 'integer',
486 'description' => 'Send a notification when someone starts to follow a user of the blog.',
487 'default' => 1,
488 )
489 );
490
491 \register_setting(
492 'activitypub_blog',
493 'activitypub_blog_user_mailer_new_mention',
494 array(
495 'type' => 'integer',
496 'description' => 'Send a notification when someone mentions a user of the blog.',
497 'default' => 1,
498 )
499 );
500
501 \register_setting(
502 'activitypub_blog',
503 'activitypub_mailer_annual_report',
504 array(
505 'type' => 'integer',
506 'description' => 'Send the annual Fediverse Year in Review email.',
507 'default' => 1,
508 )
509 );
510
511 \register_setting(
512 'activitypub_blog',
513 'activitypub_mailer_monthly_report',
514 array(
515 'type' => 'integer',
516 'description' => 'Send a monthly Fediverse stats report email.',
517 'default' => 0,
518 )
519 );
520
521 \register_setting(
522 'activitypub_blog',
523 'activitypub_blog_user_also_known_as',
524 array(
525 'type' => 'array',
526 'description' => 'An array of URLs that the blog user is known by.',
527 'default' => array(),
528 'sanitize_callback' => array( Sanitize::class, 'identifier_list' ),
529 )
530 );
531
532 \register_setting(
533 'activitypub_blog',
534 'activitypub_hide_social_graph',
535 array(
536 'type' => 'integer',
537 'description' => 'Hide Followers and Followings on Profile.',
538 'default' => 0,
539 'sanitize_callback' => 'absint',
540 'show_in_rest' => true,
541 )
542 );
543 }
544
545 /**
546 * Delete all options.
547 */
548 public static function delete() {
549 global $wpdb;
550
551 // phpcs:ignore WordPress.DB.DirectDatabaseQuery
552 $wpdb->query( "DELETE FROM $wpdb->options WHERE option_name LIKE 'activitypub_%'" );
553 }
554
555 /**
556 * Pre-get option filter for the Actor-Mode.
557 *
558 * @param string|false $pre The pre-get option value.
559 *
560 * @return string|false The actor mode or false if it should not be filtered.
561 */
562 public static function pre_option_activitypub_actor_mode( $pre ) {
563 if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) {
564 return ACTIVITYPUB_BLOG_MODE;
565 }
566
567 if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) && ACTIVITYPUB_DISABLE_USER ) {
568 return ACTIVITYPUB_BLOG_MODE;
569 }
570
571 if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) && ACTIVITYPUB_DISABLE_BLOG_USER ) {
572 return ACTIVITYPUB_ACTOR_MODE;
573 }
574
575 return $pre;
576 }
577
578 /**
579 * Pre-get option filter for the Authorized Fetch.
580 *
581 * @param string $pre The pre-get option value.
582 *
583 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
584 */
585 public static function pre_option_activitypub_authorized_fetch( $pre ) {
586 if ( ! \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
587 return $pre;
588 }
589
590 if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
591 return '1';
592 }
593
594 return '0';
595 }
596
597 /**
598 * Pre-get option filter for the Vary Header.
599 *
600 * @param string $pre The pre-get option value.
601 *
602 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
603 */
604 public static function pre_option_activitypub_vary_header( $pre ) {
605 if ( ! \defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) ) {
606 return $pre;
607 }
608
609 if ( ACTIVITYPUB_SEND_VARY_HEADER ) {
610 return '1';
611 }
612
613 return '0';
614 }
615
616 /**
617 * Pre-get option filter for the Following UI.
618 *
619 * Forces the Following UI to be enabled when the Reader is enabled.
620 *
621 * @param string $pre The pre-get option value.
622 *
623 * @return string If the Reader is enabled, return '1', otherwise return the pre-get option value.
624 */
625 public static function pre_option_activitypub_following_ui( $pre ) {
626 /*
627 * Bypass the filter to get the actual stored value for activitypub_reader_ui.
628 * This avoids infinite loops if activitypub_reader_ui also had a pre_option filter.
629 */
630 if ( \get_option( 'activitypub_reader_ui', '0' ) ) {
631 return '1';
632 }
633
634 return $pre;
635 }
636
637 /**
638 * Pre-get option filter for the Create Posts setting.
639 *
640 * Forces the Create Posts setting to be enabled when the Reader is enabled.
641 *
642 * @param string $pre The pre-get option value.
643 *
644 * @return string If the Reader is enabled, return '1', otherwise return the pre-get option value.
645 */
646 public static function pre_option_activitypub_create_posts( $pre ) {
647 if ( \get_option( 'activitypub_reader_ui', '0' ) ) {
648 return '1';
649 }
650
651 return $pre;
652 }
653
654 /**
655 * Disallow interactions if the constant is set.
656 *
657 * @param bool $pre The value of the option.
658 *
659 * @return bool|string The value of the option.
660 */
661 public static function maybe_disable_interactions( $pre ) {
662 if ( ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
663 return '0';
664 }
665
666 return $pre;
667 }
668
669 /**
670 * Default option filter for the Content-Negotiation.
671 *
672 * @see https://github.com/Automattic/wordpress-activitypub/wiki/Caching
673 *
674 * @param string $default_value The default value of the option.
675 *
676 * @return string The default value of the option.
677 */
678 public static function default_option_activitypub_negotiate_content( $default_value ) {
679 $disable_for_plugins = array(
680 'wp-optimize/wp-optimize.php',
681 'wp-rocket/wp-rocket.php',
682 'w3-total-cache/w3-total-cache.php',
683 'wp-fastest-cache/wp-fastest-cache.php',
684 'sg-cachepress/sg-cachepress.php',
685 );
686
687 foreach ( $disable_for_plugins as $plugin ) {
688 if ( \is_plugin_active( $plugin ) ) {
689 return '0';
690 }
691 }
692
693 return $default_value;
694 }
695
696 /**
697 * Default max image attachments.
698 *
699 * @param string $value The value of the option.
700 *
701 * @return string|int The value of the option.
702 */
703 public static function default_max_image_attachments( $value ) {
704 if ( ! \is_numeric( $value ) ) {
705 $value = ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS;
706 }
707
708 return $value;
709 }
710
711 /**
712 * Ensure support post types is an array.
713 *
714 * @param string[] $value The value of the option.
715 *
716 * @return string[] The value of the option.
717 */
718 public static function support_post_types_ensure_array( $value ) {
719 return (array) $value;
720 }
721
722 /**
723 * Default object type.
724 *
725 * @param string $value The value of the option.
726 *
727 * @return string The value of the option.
728 */
729 public static function default_object_type( $value ) {
730 if ( ! $value ) {
731 $value = ACTIVITYPUB_DEFAULT_OBJECT_TYPE;
732 }
733
734 return $value;
735 }
736
737 /**
738 * Pre-get option filter for the Distribution Mode.
739 *
740 * @since 9.0.0
741 *
742 * @param string|false $pre The pre-get option value.
743 *
744 * @return string|false The distribution mode or false if it should not be filtered.
745 */
746 public static function pre_option_activitypub_distribution_mode( $pre ) {
747 return self::resolve_distribution_mode( $pre, ACTIVITYPUB_DISTRIBUTION_MODE );
748 }
749
750 /**
751 * Whether the distribution mode is locked to a valid preset by the
752 * `ACTIVITYPUB_DISTRIBUTION_MODE` constant.
753 *
754 * Returns true only when the constant is set to a key recognized by
755 * `get_distribution_preset_values()`. Invalid constant values fall back
756 * to `'default'` at runtime (see `resolve_distribution_mode()`) but the
757 * UI stays visible so admins can spot the misconfiguration.
758 *
759 * @since 9.0.0
760 *
761 * @return bool True when the constant pins the mode to a valid preset.
762 */
763 public static function is_distribution_mode_locked() {
764 if ( false === ACTIVITYPUB_DISTRIBUTION_MODE ) {
765 return false;
766 }
767
768 return \in_array( ACTIVITYPUB_DISTRIBUTION_MODE, \array_keys( self::get_distribution_preset_values() ), true );
769 }
770
771 /**
772 * Resolve the distribution mode against the wp-config constant.
773 *
774 * Extracted from `pre_option_activitypub_distribution_mode()` so the
775 * constant-lock path can be exercised from tests without redefining
776 * the real constant.
777 *
778 * Only preset modes are honored via the constant. The 'custom' mode
779 * is excluded because its batch size and pause values are still read
780 * from the database, which would defeat the purpose of locking the
781 * mode via wp-config.php.
782 *
783 * @since 9.0.0
784 *
785 * @param string|false $pre The pre-get option value.
786 * @param mixed $constant_value The value of `ACTIVITYPUB_DISTRIBUTION_MODE`.
787 *
788 * @return string|false Mode if locked, `$pre` otherwise.
789 */
790 public static function resolve_distribution_mode( $pre, $constant_value ) {
791 if ( false === $constant_value ) {
792 return $pre;
793 }
794
795 $allowed = \array_keys( self::get_distribution_preset_values() );
796
797 if ( \in_array( $constant_value, $allowed, true ) ) {
798 return $constant_value;
799 }
800
801 \_doing_it_wrong(
802 __METHOD__,
803 \sprintf(
804 /* translators: %s: invalid constant value */
805 \esc_html__( 'ACTIVITYPUB_DISTRIBUTION_MODE value %s is not a valid preset; falling back to default.', 'activitypub' ),
806 \esc_html( (string) $constant_value )
807 ),
808 '9.0.0'
809 );
810
811 return 'default';
812 }
813
814 /**
815 * Get the raw batch_size/pause values for each distribution preset.
816 *
817 * Single source of truth for the preset values, used in the hot path
818 * (get_distribution_params, sanitize_distribution_mode, resolve_distribution_mode)
819 * to avoid running translation calls just to check keys or numbers.
820 *
821 * @since 9.0.0
822 *
823 * @return array Associative array of mode => { batch_size, pause }.
824 */
825 private static function get_distribution_preset_values() {
826 return array(
827 'default' => array(
828 'batch_size' => 100,
829 'pause' => 15,
830 ),
831 'balanced' => array(
832 'batch_size' => 50,
833 'pause' => 30,
834 ),
835 'eco' => array(
836 'batch_size' => 20,
837 'pause' => 30,
838 ),
839 );
840 }
841
842 /**
843 * Get the available distribution mode presets with UI labels.
844 *
845 * Decorates `get_distribution_preset_values()` with translated labels
846 * and descriptions for use in the admin settings page.
847 *
848 * @since 9.0.0
849 *
850 * @return array Associative array of mode => { batch_size, pause, label, description }.
851 */
852 public static function get_distribution_modes() {
853 $modes = self::get_distribution_preset_values();
854
855 $modes['default']['label'] = \__( 'Default', 'activitypub' );
856 $modes['default']['description'] = \sprintf(
857 /* translators: 1: batch size, 2: pause in seconds */
858 \__( 'Deliver activities as fast as possible (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
859 $modes['default']['batch_size'],
860 $modes['default']['pause']
861 );
862 $modes['balanced']['label'] = \__( 'Balanced', 'activitypub' );
863 $modes['balanced']['description'] = \sprintf(
864 /* translators: 1: batch size, 2: pause in seconds */
865 \__( 'Moderate pace with reasonable pauses between batches (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
866 $modes['balanced']['batch_size'],
867 $modes['balanced']['pause']
868 );
869 $modes['eco']['label'] = \__( 'Eco Mode', 'activitypub' );
870 $modes['eco']['description'] = \sprintf(
871 /* translators: 1: batch size, 2: pause in seconds */
872 \__( 'Gentle on server resources, ideal for shared hosting (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
873 $modes['eco']['batch_size'],
874 $modes['eco']['pause']
875 );
876
877 return $modes;
878 }
879
880 /**
881 * Sanitize the distribution mode option.
882 *
883 * Restricts the stored value to a known preset (from
884 * `get_distribution_modes()`) or `'custom'`. Anything else
885 * falls back to `'default'`.
886 *
887 * @since 9.0.0
888 *
889 * @param string $value The submitted option value.
890 *
891 * @return string A valid distribution mode key.
892 */
893 public static function sanitize_distribution_mode( $value ) {
894 $allowed = \array_merge( \array_keys( self::get_distribution_preset_values() ), array( 'custom' ) );
895
896 return \in_array( $value, $allowed, true ) ? $value : 'default';
897 }
898
899 /**
900 * Get distribution parameters for the current mode.
901 *
902 * @since 9.0.0
903 *
904 * @return array { mode: string, batch_size: int, pause: int }
905 */
906 public static function get_distribution_params() {
907 $mode = \get_option( 'activitypub_distribution_mode', 'default' );
908 $modes = self::get_distribution_preset_values();
909
910 if ( isset( $modes[ $mode ] ) ) {
911 return array(
912 'mode' => $mode,
913 'batch_size' => $modes[ $mode ]['batch_size'],
914 'pause' => $modes[ $mode ]['pause'],
915 );
916 }
917
918 // Custom mode reads its values from dedicated options; any other
919 // unrecognized mode falls back to the default preset so callers
920 // always receive a valid configuration.
921 if ( 'custom' !== $mode ) {
922 return array(
923 'mode' => 'default',
924 'batch_size' => $modes['default']['batch_size'],
925 'pause' => $modes['default']['pause'],
926 );
927 }
928
929 $default_params = $modes['default'];
930
931 return array(
932 'mode' => 'custom',
933 'batch_size' => \max( 1, \absint( \get_option( 'activitypub_custom_batch_size', $default_params['batch_size'] ) ) ),
934 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', $default_params['pause'] ) ),
935 );
936 }
937
938 /**
939 * Filter the dispatcher batch size based on distribution mode.
940 *
941 * In `'default'` mode the upstream value is passed through so the
942 * `ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE` constant and other filters
943 * still win; any explicit mode imposes its own batch size.
944 *
945 * @since 9.0.0
946 *
947 * @param int $batch_size The default batch size.
948 *
949 * @return int The batch size for the current distribution mode.
950 */
951 public static function filter_dispatcher_batch_size( $batch_size ) {
952 $params = self::get_distribution_params();
953
954 return 'default' === $params['mode'] ? $batch_size : $params['batch_size'];
955 }
956
957 /**
958 * Filter the scheduler batch pause based on distribution mode.
959 *
960 * Only delivery batches (`activitypub_send_activity`) are affected. Every
961 * mode imposes its own delivery pause: `'default'` is the fast preset, which
962 * is intentionally shorter than the generic async-batch baseline, so it does
963 * not pass the upstream value through.
964 *
965 * @since 9.0.0
966 *
967 * @param int $pause The default pause in seconds.
968 * @param string|false|null $hook The async batch hook being scheduled.
969 *
970 * @return int The pause for the current distribution mode.
971 */
972 public static function filter_scheduler_batch_pause( $pause, $hook = null ) {
973 if ( 'activitypub_send_activity' !== $hook ) {
974 return $pause;
975 }
976
977 return self::get_distribution_params()['pause'];
978 }
979
980 /**
981 * Sanitize purge day values.
982 *
983 * Ensures the value is a non-negative integer. Returns the
984 * registered default when the stored value is empty or false
985 * (option not properly set), but allows 0 to disable purging.
986 *
987 * @since 8.1.0
988 *
989 * @param mixed $value The stored option value.
990 *
991 * @return int The sanitized value.
992 */
993 public static function sanitize_purge_days( $value ) {
994 if ( '' === $value || false === $value ) {
995 $filter = \current_filter();
996 $defaults = array(
997 'option_activitypub_outbox_purge_days' => ACTIVITYPUB_OUTBOX_PURGE_DAYS,
998 'option_activitypub_inbox_purge_days' => ACTIVITYPUB_INBOX_PURGE_DAYS,
999 'option_activitypub_ap_post_purge_days' => ACTIVITYPUB_AP_POST_PURGE_DAYS,
1000 );
1001
1002 return $defaults[ $filter ] ?? ACTIVITYPUB_OUTBOX_PURGE_DAYS;
1003 }
1004
1005 return \max( 1, \absint( $value ) );
1006 }
1007
1008 /**
1009 * Handle relay mode option changes.
1010 *
1011 * When relay mode is enabled, switch to blog-only mode and set username to "relay".
1012 * When disabled, restore previous settings.
1013 *
1014 * @param mixed $old_value The old option value.
1015 * @param mixed $new_value The new option value.
1016 */
1017 public static function relay_mode_changed( $old_value, $new_value ) {
1018 if ( $new_value && ! $old_value ) {
1019 // Enabling relay mode.
1020 // Store previous username and actor mode for restoration.
1021 \update_option( 'activitypub_relay_previous_blog_identifier', \get_option( 'activitypub_blog_identifier' ) );
1022 \update_option( 'activitypub_relay_previous_actor_mode', \get_option( 'activitypub_actor_mode' ) );
1023
1024 // Set blog username to "relay".
1025 \update_option( 'activitypub_blog_identifier', 'relay' );
1026
1027 // Switch to blog-only mode.
1028 \update_option( 'activitypub_actor_mode', ACTIVITYPUB_BLOG_MODE );
1029 } elseif ( ! $new_value && $old_value ) {
1030 // Disabling relay mode - restore previous settings.
1031 $previous_identifier = \get_option( 'activitypub_relay_previous_blog_identifier' );
1032 $previous_actor_mode = \get_option( 'activitypub_relay_previous_actor_mode' );
1033
1034 if ( $previous_identifier ) {
1035 \update_option( 'activitypub_blog_identifier', $previous_identifier );
1036 \delete_option( 'activitypub_relay_previous_blog_identifier' );
1037 }
1038
1039 if ( $previous_actor_mode ) {
1040 \update_option( 'activitypub_actor_mode', $previous_actor_mode );
1041 \delete_option( 'activitypub_relay_previous_actor_mode' );
1042 }
1043 }
1044 }
1045 }
1046