From d5821d849c73f6f948d9e6b05cababef4f042db7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Tue, 17 Mar 2026 10:55:45 +0100 Subject: [PATCH 1/7] Add configurable distribution modes for federation delivery Adds a "Distribution Mode" setting to the Advanced Settings page with four presets (Default, Balanced, Eco, Custom) that control batch size and pause between batches for federation delivery. Includes a constant override via ACTIVITYPUB_DISTRIBUTION_MODE in wp-config.php. Fixes #2672 --- includes/class-options.php | 121 ++++++++++++++++++ includes/constants.php | 1 + .../class-advanced-settings-fields.php | 92 +++++++++++++ 3 files changed, 214 insertions(+) diff --git a/includes/class-options.php b/includes/class-options.php index 1b63697673..4f988c6363 100644 --- a/includes/class-options.php +++ b/includes/class-options.php @@ -27,6 +27,10 @@ public static function init() { \add_filter( 'pre_option_activitypub_following_ui', array( self::class, 'pre_option_activitypub_following_ui' ) ); \add_filter( 'pre_option_activitypub_create_posts', array( self::class, 'pre_option_activitypub_create_posts' ) ); + \add_filter( 'pre_option_activitypub_distribution_mode', array( self::class, 'pre_option_activitypub_distribution_mode' ) ); + \add_filter( 'activitypub_dispatcher_batch_size', array( self::class, 'filter_dispatcher_batch_size' ) ); + \add_filter( 'activitypub_scheduler_async_batch_pause', array( self::class, 'filter_scheduler_batch_pause' ) ); + \add_filter( 'pre_option_activitypub_allow_likes', array( self::class, 'maybe_disable_interactions' ) ); \add_filter( 'pre_option_activitypub_allow_replies', array( self::class, 'maybe_disable_interactions' ) ); @@ -358,6 +362,42 @@ public static function register_settings() { ) ); + \register_setting( + 'activitypub_advanced', + 'activitypub_distribution_mode', + array( + 'type' => 'string', + 'description' => 'Distribution mode for federation delivery.', + 'default' => 'default', + 'sanitize_callback' => static function ( $value ) { + $allowed = array( 'default', 'balanced', 'eco', 'custom' ); + return \in_array( $value, $allowed, true ) ? $value : 'default'; + }, + ) + ); + + \register_setting( + 'activitypub_advanced', + 'activitypub_custom_batch_size', + array( + 'type' => 'integer', + 'description' => 'Custom batch size for federation delivery.', + 'default' => 100, + 'sanitize_callback' => 'absint', + ) + ); + + \register_setting( + 'activitypub_advanced', + 'activitypub_custom_batch_pause', + array( + 'type' => 'integer', + 'description' => 'Custom pause in seconds between batches.', + 'default' => 30, + 'sanitize_callback' => 'absint', + ) + ); + /* * Options Group: activitypub_blog */ @@ -640,6 +680,87 @@ public static function default_object_type( $value ) { return $value; } + /** + * Pre-get option filter for the Distribution Mode. + * + * @since unreleased + * + * @param string|false $pre The pre-get option value. + * + * @return string|false The distribution mode or false if it should not be filtered. + */ + public static function pre_option_activitypub_distribution_mode( $pre ) { + if ( false !== ACTIVITYPUB_DISTRIBUTION_MODE ) { + return ACTIVITYPUB_DISTRIBUTION_MODE; + } + + return $pre; + } + + /** + * Get distribution parameters for the current mode. + * + * @since unreleased + * + * @return array { batch_size: int, pause: int } + */ + public static function get_distribution_params() { + $mode = \get_option( 'activitypub_distribution_mode', 'default' ); + + $modes = array( + 'default' => array( + 'batch_size' => 100, + 'pause' => 30, + ), + 'balanced' => array( + 'batch_size' => 50, + 'pause' => 60, + ), + 'eco' => array( + 'batch_size' => 20, + 'pause' => 300, + ), + ); + + if ( isset( $modes[ $mode ] ) ) { + return $modes[ $mode ]; + } + + // Custom mode. + return array( + 'batch_size' => \absint( \get_option( 'activitypub_custom_batch_size', 100 ) ), + 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', 30 ) ), + ); + } + + /** + * Filter the dispatcher batch size based on distribution mode. + * + * @since unreleased + * + * @param int $batch_size The default batch size. + * + * @return int The batch size for the current distribution mode. + */ + public static function filter_dispatcher_batch_size( $batch_size ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $params = self::get_distribution_params(); + return $params['batch_size']; + } + + /** + * Filter the scheduler batch pause based on distribution mode. + * + * @since unreleased + * + * @param int $pause The default pause in seconds. + * + * @return int The pause for the current distribution mode. + */ + public static function filter_scheduler_batch_pause( $pause ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $params = self::get_distribution_params(); + return $params['pause']; + } + /** * Handle relay mode option changes. * diff --git a/includes/constants.php b/includes/constants.php index 8c192d511b..2e14170c5d 100644 --- a/includes/constants.php +++ b/includes/constants.php @@ -20,6 +20,7 @@ defined( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS' ) || define( 'ACTIVITYPUB_DISABLE_OUTGOING_INTERACTIONS', false ); defined( 'ACTIVITYPUB_DEFAULT_OBJECT_TYPE' ) || define( 'ACTIVITYPUB_DEFAULT_OBJECT_TYPE', 'wordpress-post-format' ); defined( 'ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE' ) || define( 'ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE', 100 ); +defined( 'ACTIVITYPUB_DISTRIBUTION_MODE' ) || define( 'ACTIVITYPUB_DISTRIBUTION_MODE', false ); // Backwards compatibility: map old ACTIVITYPUB_DISABLE_SIDELOADING to ACTIVITYPUB_DISABLE_REMOTE_CACHE. if ( ! defined( 'ACTIVITYPUB_DISABLE_REMOTE_CACHE' ) && defined( 'ACTIVITYPUB_DISABLE_SIDELOADING' ) ) { define( 'ACTIVITYPUB_DISABLE_REMOTE_CACHE', ACTIVITYPUB_DISABLE_SIDELOADING ); diff --git a/includes/wp-admin/class-advanced-settings-fields.php b/includes/wp-admin/class-advanced-settings-fields.php index 00fe100335..b15e1bded5 100644 --- a/includes/wp-admin/class-advanced-settings-fields.php +++ b/includes/wp-admin/class-advanced-settings-fields.php @@ -30,6 +30,16 @@ public static function register_advanced_fields() { 'activitypub_advanced_settings' ); + if ( ! ACTIVITYPUB_DISTRIBUTION_MODE ) { + \add_settings_field( + 'activitypub_distribution_mode', + \__( 'Distribution Mode', 'activitypub' ), + array( self::class, 'render_distribution_mode_field' ), + 'activitypub_advanced_settings', + 'activitypub_advanced_settings' + ); + } + if ( ! defined( 'ACTIVITYPUB_SEND_VARY_HEADER' ) ) { \add_settings_field( 'activitypub_vary_header', @@ -288,4 +298,86 @@ public static function render_object_type_field() {

array( + 'label' => \__( 'Default', 'activitypub' ), + 'description' => \__( 'Deliver activities as fast as possible (100 per batch, 30s pause).', 'activitypub' ), + ), + 'balanced' => array( + 'label' => \__( 'Balanced', 'activitypub' ), + 'description' => \__( 'Moderate pace with reasonable pauses between batches (50 per batch, 60s pause).', 'activitypub' ), + ), + 'eco' => array( + 'label' => \__( 'Eco Mode', 'activitypub' ), + 'description' => \__( 'Gentle on server resources, ideal for shared hosting (20 per batch, 5min pause).', 'activitypub' ), + ), + 'custom' => array( + 'label' => \__( 'Custom', 'activitypub' ), + 'description' => \__( 'Configure batch size and delay manually.', 'activitypub' ), + ), + ); + + ?> +
+ +

+ +

+ $data ) { + ?> +

+ +
+ array() ) ); ?> +

+ + +

+ +

+
+ + Date: Tue, 17 Mar 2026 10:56:39 +0100 Subject: [PATCH 2/7] Add changelog --- .github/changelog/3044-from-description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/changelog/3044-from-description diff --git a/.github/changelog/3044-from-description b/.github/changelog/3044-from-description new file mode 100644 index 0000000000..8d3cbfd485 --- /dev/null +++ b/.github/changelog/3044-from-description @@ -0,0 +1,4 @@ +Significance: patch +Type: added + +Add a Distribution Mode setting to control how quickly posts are delivered to followers. From 5bb502dff7557c75de9e3eacef005dc7168f3aa5 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 30 Mar 2026 18:14:50 +0200 Subject: [PATCH 3/7] Address review feedback for distribution mode settings - Fix inconsistent constant check: use `false ===` in both the UI visibility check and the option override - Validate ACTIVITYPUB_DISTRIBUTION_MODE constant against allowed modes, falling back to 'default' for invalid values - Centralize preset definitions in Options::get_distribution_modes() and reuse in both admin UI and parameter resolution - Add static cache in get_distribution_params() to avoid rebuilding presets on every filter call during delivery - Null-guard the custom fields DOM element in inline script --- includes/class-options.php | 76 ++++++++++++++----- .../class-advanced-settings-fields.php | 29 +++---- 2 files changed, 68 insertions(+), 37 deletions(-) diff --git a/includes/class-options.php b/includes/class-options.php index 345665dc32..a71ab5fa8d 100644 --- a/includes/class-options.php +++ b/includes/class-options.php @@ -711,46 +711,84 @@ public static function default_object_type( $value ) { */ public static function pre_option_activitypub_distribution_mode( $pre ) { if ( false !== ACTIVITYPUB_DISTRIBUTION_MODE ) { - return ACTIVITYPUB_DISTRIBUTION_MODE; + $allowed = array_keys( self::get_distribution_modes() ); + $allowed[] = 'custom'; + + if ( \in_array( ACTIVITYPUB_DISTRIBUTION_MODE, $allowed, true ) ) { + return ACTIVITYPUB_DISTRIBUTION_MODE; + } + + // Invalid constant value, fall back to default. + return 'default'; } return $pre; } /** - * Get distribution parameters for the current mode. + * Get the available distribution mode presets. + * + * Centralized definition used by both the admin UI and the + * parameter resolution in get_distribution_params(). * * @since unreleased * - * @return array { batch_size: int, pause: int } + * @return array Associative array of mode => { batch_size, pause, label, description }. */ - public static function get_distribution_params() { - $mode = \get_option( 'activitypub_distribution_mode', 'default' ); - - $modes = array( + public static function get_distribution_modes() { + return array( 'default' => array( - 'batch_size' => 100, - 'pause' => 30, + 'batch_size' => 100, + 'pause' => 30, + 'label' => \__( 'Default', 'activitypub' ), + 'description' => \__( 'Deliver activities as fast as possible (100 per batch, 30s pause).', 'activitypub' ), ), 'balanced' => array( - 'batch_size' => 50, - 'pause' => 60, + 'batch_size' => 50, + 'pause' => 60, + 'label' => \__( 'Balanced', 'activitypub' ), + 'description' => \__( 'Moderate pace with reasonable pauses between batches (50 per batch, 60s pause).', 'activitypub' ), ), 'eco' => array( - 'batch_size' => 20, - 'pause' => 300, + 'batch_size' => 20, + 'pause' => 300, + 'label' => \__( 'Eco Mode', 'activitypub' ), + 'description' => \__( 'Gentle on server resources, ideal for shared hosting (20 per batch, 5min pause).', 'activitypub' ), ), ); + } + + /** + * Get distribution parameters for the current mode. + * + * @since unreleased + * + * @return array { batch_size: int, pause: int } + */ + public static function get_distribution_params() { + static $cached = null; + + if ( null !== $cached ) { + return $cached; + } + + $mode = \get_option( 'activitypub_distribution_mode', 'default' ); + $modes = self::get_distribution_modes(); if ( isset( $modes[ $mode ] ) ) { - return $modes[ $mode ]; + $cached = array( + 'batch_size' => $modes[ $mode ]['batch_size'], + 'pause' => $modes[ $mode ]['pause'], + ); + } else { + // Custom mode. + $cached = array( + 'batch_size' => \absint( \get_option( 'activitypub_custom_batch_size', 100 ) ), + 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', 30 ) ), + ); } - // Custom mode. - return array( - 'batch_size' => \absint( \get_option( 'activitypub_custom_batch_size', 100 ) ), - 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', 30 ) ), - ); + return $cached; } /** diff --git a/includes/wp-admin/class-advanced-settings-fields.php b/includes/wp-admin/class-advanced-settings-fields.php index b15e1bded5..fdbf389e92 100644 --- a/includes/wp-admin/class-advanced-settings-fields.php +++ b/includes/wp-admin/class-advanced-settings-fields.php @@ -7,6 +7,8 @@ namespace Activitypub\WP_Admin; +use Activitypub\Options; + /** * Advanced Settings Fields class. */ @@ -30,7 +32,7 @@ public static function register_advanced_fields() { 'activitypub_advanced_settings' ); - if ( ! ACTIVITYPUB_DISTRIBUTION_MODE ) { + if ( false === ACTIVITYPUB_DISTRIBUTION_MODE ) { \add_settings_field( 'activitypub_distribution_mode', \__( 'Distribution Mode', 'activitypub' ), @@ -310,23 +312,11 @@ public static function render_distribution_mode_field() { $custom_pause = \get_option( 'activitypub_custom_batch_pause', 30 ); $is_custom = 'custom' === $mode; - $modes = array( - 'default' => array( - 'label' => \__( 'Default', 'activitypub' ), - 'description' => \__( 'Deliver activities as fast as possible (100 per batch, 30s pause).', 'activitypub' ), - ), - 'balanced' => array( - 'label' => \__( 'Balanced', 'activitypub' ), - 'description' => \__( 'Moderate pace with reasonable pauses between batches (50 per batch, 60s pause).', 'activitypub' ), - ), - 'eco' => array( - 'label' => \__( 'Eco Mode', 'activitypub' ), - 'description' => \__( 'Gentle on server resources, ideal for shared hosting (20 per batch, 5min pause).', 'activitypub' ), - ), - 'custom' => array( - 'label' => \__( 'Custom', 'activitypub' ), - 'description' => \__( 'Configure batch size and delay manually.', 'activitypub' ), - ), + // Use centralized presets and add the custom option for the UI. + $modes = Options::get_distribution_modes(); + $modes['custom'] = array( + 'label' => \__( 'Custom', 'activitypub' ), + 'description' => \__( 'Configure batch size and delay manually.', 'activitypub' ), ); ?> @@ -371,6 +361,9 @@ public static function render_distribution_mode_field() { ( function() { var radios = document.querySelectorAll( 'input[name="activitypub_distribution_mode"]' ); var fields = document.getElementById( 'activitypub-custom-distribution-fields' ); + if ( ! fields ) { + return; + } radios.forEach( function( radio ) { radio.addEventListener( 'change', function() { fields.style.display = this.value === 'custom' ? '' : 'none'; From 03c4f0bf179f90899b518986cb5036980ce148a7 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Mon, 30 Mar 2026 18:16:08 +0200 Subject: [PATCH 4/7] Document no-inline-namespaces convention in AGENTS.md Use `use` statements at the top of files instead of inline fully-qualified class names like \Activitypub\Options::method(). --- AGENTS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 0092dcd454..6ac0449f68 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -88,6 +88,8 @@ Text domain: always `'activitypub'`. **MUST** backslash-prefix all WordPress functions in namespaced code: `\get_option()`, `\add_action()`, `\apply_filters()`, `\__()`, `\_e()`, etc. PHP falls back to global scope, but backslashes are a project standard for consistency and to avoid accidentally shadowing globals. +**No inline namespaces.** Use `use` statements at the top of the file instead of inline fully-qualified class names (e.g., `use Activitypub\Options;` then `Options::method()`, not `\Activitypub\Options::method()`). + **For new or modified code**, MUST use `'unreleased'` for all `@since`, `@deprecated`, and deprecation function version strings so the release script can replace them. Do not introduce new hardcoded version numbers like `'5.1.0'`; existing versioned tags in the codebase are fine. ## Testing Conventions From ab9ed9c696a38eba94da2b8c331ae67fcb363a88 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 1 Apr 2026 18:49:07 +0200 Subject: [PATCH 5/7] Address review feedback for distribution modes - Prevent batch size of 0 with max(1, absint()) sanitization. - Default mode now passes through the incoming filter value so other plugins and constants are not silently overridden. - Remove static cache from get_distribution_params() since get_option() is already cached by WordPress and the static var caused stale values. - Add tests for presets, custom mode, sanitization, and filter behavior. --- includes/class-options.php | 46 ++++++---- .../tests/includes/class-test-options.php | 85 +++++++++++++++++++ 2 files changed, 114 insertions(+), 17 deletions(-) diff --git a/includes/class-options.php b/includes/class-options.php index a71ab5fa8d..904dd30a2b 100644 --- a/includes/class-options.php +++ b/includes/class-options.php @@ -383,7 +383,9 @@ public static function register_settings() { 'type' => 'integer', 'description' => 'Custom batch size for federation delivery.', 'default' => 100, - 'sanitize_callback' => 'absint', + 'sanitize_callback' => static function ( $value ) { + return \max( 1, \absint( $value ) ); + }, ) ); @@ -766,41 +768,42 @@ public static function get_distribution_modes() { * @return array { batch_size: int, pause: int } */ public static function get_distribution_params() { - static $cached = null; - - if ( null !== $cached ) { - return $cached; - } - $mode = \get_option( 'activitypub_distribution_mode', 'default' ); $modes = self::get_distribution_modes(); if ( isset( $modes[ $mode ] ) ) { - $cached = array( + return array( 'batch_size' => $modes[ $mode ]['batch_size'], 'pause' => $modes[ $mode ]['pause'], ); - } else { - // Custom mode. - $cached = array( - 'batch_size' => \absint( \get_option( 'activitypub_custom_batch_size', 100 ) ), - 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', 30 ) ), - ); } - return $cached; + // Custom mode. + return array( + 'batch_size' => \max( 1, \absint( \get_option( 'activitypub_custom_batch_size', 100 ) ) ), + 'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', 30 ) ), + ); } /** * Filter the dispatcher batch size based on distribution mode. * + * Only overrides the value when a non-default mode is active, + * so other plugins or constants can still set the batch size. + * * @since unreleased * * @param int $batch_size The default batch size. * * @return int The batch size for the current distribution mode. */ - public static function filter_dispatcher_batch_size( $batch_size ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public static function filter_dispatcher_batch_size( $batch_size ) { + $mode = \get_option( 'activitypub_distribution_mode', 'default' ); + + if ( 'default' === $mode ) { + return $batch_size; + } + $params = self::get_distribution_params(); return $params['batch_size']; } @@ -808,13 +811,22 @@ public static function filter_dispatcher_batch_size( $batch_size ) { // phpcs:ig /** * Filter the scheduler batch pause based on distribution mode. * + * Only overrides the value when a non-default mode is active, + * so other plugins or constants can still set the pause. + * * @since unreleased * * @param int $pause The default pause in seconds. * * @return int The pause for the current distribution mode. */ - public static function filter_scheduler_batch_pause( $pause ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found, VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + public static function filter_scheduler_batch_pause( $pause ) { + $mode = \get_option( 'activitypub_distribution_mode', 'default' ); + + if ( 'default' === $mode ) { + return $pause; + } + $params = self::get_distribution_params(); return $params['pause']; } diff --git a/tests/phpunit/tests/includes/class-test-options.php b/tests/phpunit/tests/includes/class-test-options.php index 61279267d2..8c969e7a84 100644 --- a/tests/phpunit/tests/includes/class-test-options.php +++ b/tests/phpunit/tests/includes/class-test-options.php @@ -42,6 +42,11 @@ public function tear_down() { // Clean up quote policy option. \delete_option( 'activitypub_default_quote_policy' ); + // Clean up distribution mode options. + \delete_option( 'activitypub_distribution_mode' ); + \delete_option( 'activitypub_custom_batch_size' ); + \delete_option( 'activitypub_custom_batch_pause' ); + parent::tear_down(); } @@ -225,6 +230,86 @@ public function test_default_quote_policy_accepts_valid_values() { $this->assertEquals( ACTIVITYPUB_INTERACTION_POLICY_ME, \get_option( 'activitypub_default_quote_policy' ) ); } + /** + * Test distribution mode returns correct params for presets. + * + * @covers \Activitypub\Options::get_distribution_params + */ + public function test_distribution_params_presets() { + \update_option( 'activitypub_distribution_mode', 'eco' ); + + $params = Options::get_distribution_params(); + + $this->assertEquals( 20, $params['batch_size'] ); + $this->assertEquals( 300, $params['pause'] ); + } + + /** + * Test distribution mode custom params. + * + * @covers \Activitypub\Options::get_distribution_params + */ + public function test_distribution_params_custom() { + Options::register_settings(); + + \update_option( 'activitypub_distribution_mode', 'custom' ); + \update_option( 'activitypub_custom_batch_size', 42 ); + \update_option( 'activitypub_custom_batch_pause', 120 ); + + $params = Options::get_distribution_params(); + + $this->assertEquals( 42, $params['batch_size'] ); + $this->assertEquals( 120, $params['pause'] ); + } + + /** + * Test custom batch size cannot be zero. + * + * @covers \Activitypub\Options::register_settings + */ + public function test_custom_batch_size_minimum() { + Options::register_settings(); + + \update_option( 'activitypub_custom_batch_size', 0 ); + $this->assertGreaterThanOrEqual( 1, \get_option( 'activitypub_custom_batch_size' ) ); + } + + /** + * Test distribution mode sanitizes invalid values. + * + * @covers \Activitypub\Options::register_settings + */ + public function test_distribution_mode_sanitizes_invalid() { + Options::register_settings(); + + \update_option( 'activitypub_distribution_mode', 'turbo' ); + $this->assertEquals( 'default', \get_option( 'activitypub_distribution_mode' ) ); + } + + /** + * Test default mode does not override filter values. + * + * @covers \Activitypub\Options::filter_dispatcher_batch_size + */ + public function test_default_mode_preserves_filter_value() { + \update_option( 'activitypub_distribution_mode', 'default' ); + + $result = Options::filter_dispatcher_batch_size( 42 ); + $this->assertEquals( 42, $result ); + } + + /** + * Test non-default mode overrides filter values. + * + * @covers \Activitypub\Options::filter_dispatcher_batch_size + */ + public function test_non_default_mode_overrides_filter_value() { + \update_option( 'activitypub_distribution_mode', 'eco' ); + + $result = Options::filter_dispatcher_batch_size( 42 ); + $this->assertEquals( 20, $result ); + } + /** * Test default quote policy option sanitizes invalid values. * From 82eb6e5d64386b6a8d56f5e41a80548da9cc7eec Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Wed, 1 Apr 2026 23:17:44 +0200 Subject: [PATCH 6/7] Add upper bounds for custom distribution values and reject custom constant - Cap custom batch size at 500 and pause at 3600s to prevent accidental server overload via misconfiguration. - Reject 'custom' as a valid ACTIVITYPUB_DISTRIBUTION_MODE constant value since its parameters are still DB-settable. - Add tests for upper bound enforcement. --- includes/class-options.php | 17 +++++++++---- .../tests/includes/class-test-options.php | 24 +++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/includes/class-options.php b/includes/class-options.php index 904dd30a2b..76bfcf1079 100644 --- a/includes/class-options.php +++ b/includes/class-options.php @@ -384,7 +384,7 @@ public static function register_settings() { 'description' => 'Custom batch size for federation delivery.', 'default' => 100, 'sanitize_callback' => static function ( $value ) { - return \max( 1, \absint( $value ) ); + return \min( 500, \max( 1, \absint( $value ) ) ); }, ) ); @@ -396,7 +396,9 @@ public static function register_settings() { 'type' => 'integer', 'description' => 'Custom pause in seconds between batches.', 'default' => 30, - 'sanitize_callback' => 'absint', + 'sanitize_callback' => static function ( $value ) { + return \min( 3600, \absint( $value ) ); + }, ) ); @@ -713,14 +715,19 @@ public static function default_object_type( $value ) { */ public static function pre_option_activitypub_distribution_mode( $pre ) { if ( false !== ACTIVITYPUB_DISTRIBUTION_MODE ) { - $allowed = array_keys( self::get_distribution_modes() ); - $allowed[] = 'custom'; + /* + * Only preset modes are allowed via the constant. The 'custom' + * mode is excluded because its batch size and pause values are + * still read from the database, which defeats the purpose of + * locking the mode via wp-config.php. + */ + $allowed = array_keys( self::get_distribution_modes() ); if ( \in_array( ACTIVITYPUB_DISTRIBUTION_MODE, $allowed, true ) ) { return ACTIVITYPUB_DISTRIBUTION_MODE; } - // Invalid constant value, fall back to default. + // Invalid or unsupported constant value, fall back to default. return 'default'; } diff --git a/tests/phpunit/tests/includes/class-test-options.php b/tests/phpunit/tests/includes/class-test-options.php index 8c969e7a84..ca7ae7748a 100644 --- a/tests/phpunit/tests/includes/class-test-options.php +++ b/tests/phpunit/tests/includes/class-test-options.php @@ -274,6 +274,30 @@ public function test_custom_batch_size_minimum() { $this->assertGreaterThanOrEqual( 1, \get_option( 'activitypub_custom_batch_size' ) ); } + /** + * Test custom batch size is capped at 500. + * + * @covers \Activitypub\Options::register_settings + */ + public function test_custom_batch_size_maximum() { + Options::register_settings(); + + \update_option( 'activitypub_custom_batch_size', 100000 ); + $this->assertEquals( 500, \get_option( 'activitypub_custom_batch_size' ) ); + } + + /** + * Test custom batch pause is capped at 3600. + * + * @covers \Activitypub\Options::register_settings + */ + public function test_custom_batch_pause_maximum() { + Options::register_settings(); + + \update_option( 'activitypub_custom_batch_pause', 99999 ); + $this->assertEquals( 3600, \get_option( 'activitypub_custom_batch_pause' ) ); + } + /** * Test distribution mode sanitizes invalid values. * From 7a84430386835ba1093452eaee87bc8f9d4ec4ea Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 2 Apr 2026 08:59:47 +0200 Subject: [PATCH 7/7] Adjust distribution mode pause values to 15s, 30s, 30s MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default 15s, Balanced 30s, Eco 30s — the differentiation between modes is now purely batch size (100/50/20). The previous 5min Eco pause caused unreasonably long delivery times. --- includes/class-options.php | 12 ++++++------ includes/wp-admin/class-advanced-settings-fields.php | 2 +- tests/phpunit/tests/includes/class-test-options.php | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/includes/class-options.php b/includes/class-options.php index 76bfcf1079..dd7b4295c8 100644 --- a/includes/class-options.php +++ b/includes/class-options.php @@ -748,21 +748,21 @@ public static function get_distribution_modes() { return array( 'default' => array( 'batch_size' => 100, - 'pause' => 30, + 'pause' => 15, 'label' => \__( 'Default', 'activitypub' ), - 'description' => \__( 'Deliver activities as fast as possible (100 per batch, 30s pause).', 'activitypub' ), + 'description' => \__( 'Deliver activities as fast as possible (100 per batch, 15s pause).', 'activitypub' ), ), 'balanced' => array( 'batch_size' => 50, - 'pause' => 60, + 'pause' => 30, 'label' => \__( 'Balanced', 'activitypub' ), - 'description' => \__( 'Moderate pace with reasonable pauses between batches (50 per batch, 60s pause).', 'activitypub' ), + 'description' => \__( 'Moderate pace with reasonable pauses between batches (50 per batch, 30s pause).', 'activitypub' ), ), 'eco' => array( 'batch_size' => 20, - 'pause' => 300, + 'pause' => 30, 'label' => \__( 'Eco Mode', 'activitypub' ), - 'description' => \__( 'Gentle on server resources, ideal for shared hosting (20 per batch, 5min pause).', 'activitypub' ), + 'description' => \__( 'Gentle on server resources, ideal for shared hosting (20 per batch, 30s pause).', 'activitypub' ), ), ); } diff --git a/includes/wp-admin/class-advanced-settings-fields.php b/includes/wp-admin/class-advanced-settings-fields.php index fdbf389e92..c42d0aa33d 100644 --- a/includes/wp-admin/class-advanced-settings-fields.php +++ b/includes/wp-admin/class-advanced-settings-fields.php @@ -354,7 +354,7 @@ public static function render_distribution_mode_field() {

- +