Repository: Automattic/wordpress-activitypub
Pull request #1449: Advanced Settings
Author: login: pfefferle
Labels: name: Enhancement
description: New feature or request
name: Actions
name: [Feature] WP Admin
Created: 2025-03-12T09:57:13Z | Merged: 2025-04-02T19:39:11Z

Not all settings are meant for every user, because they might change the behaviour of a site drastically! So in a discussion with @jeherve we came up with the idea of advanced settings. These settings are not public by default (for now it requires to know the query param, but we can also add a setting to the default settings: "Enable Advanced Settings", to always show the tab).

For now I only migrated the settings, I think might sense to not be permanently user facing and I would love to add more in the future.

Other "Advanced Settings":

* Enable/Disable `Vary` header
* Enable/Disable `Content Negotiation` for pretty permalinks

<img width="1056" alt="Screenshot 2025-03-12 at 10 47 43" src="https://github.com/user-attachments/assets/1fbafa5f-c06d-4b2f-9abf-2a56c67d83be" />

props @obenland for the nice restructuring of the settings, that makes allows adding new tabs so easily!

## Proposed changes:
<!--- Explain what functional changes your PR includes -->
* Add a new (hidden) tab for "Advanced Settings".
* Migration to always show the advanced settings, if they were already changed.

### Other information:

- [ ] Have you written new tests for your changes, if applicable?

## Testing instructions:
<!-- If you were reviewing this PR, how would you like the instructions to be presented? -->
<!-- Please include detailed testing steps, explaining how to test your change. -->
<!-- Bear in mind that context you working on is not obvious for everyone.  -->
<!-- Adding "simple" configuration steps will help reviewers to get to your PR as quickly as possible. -->
<!-- "Before / After" screenshots can also be very helpful when the change is visual. -->

* Go to `/wp-admin/options-general.php?page=activitypub&tab=advanced` and check the settings




Diff:
diff --git a/.github/changelog/add-advanced-settings b/.github/changelog/add-advanced-settings
new file mode 100644
index 000000000..a6a162977
--- /dev/null
+++ b/.github/changelog/add-advanced-settings
@@ -0,0 +1,4 @@
+Significance: minor
+Type: added
+
+Advanced Settings tab, with special settings for advanced users.
diff --git a/activitypub.php b/activitypub.php
index 1b89ee8ae..12d1bea4c 100644
--- a/activitypub.php
+++ b/activitypub.php
@@ -100,6 +100,7 @@ function plugin_admin_init() {
 	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\Settings', 'init' ) );
 	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\Settings_Fields', 'init' ) );
 	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\Welcome_Fields', 'init' ) );
+	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\Advanced_Settings_Fields', 'init' ) );
 	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\Blog_Settings_Fields', 'init' ) );
 	\add_action( 'admin_init', array( __NAMESPACE__ . '\WP_Admin\User_Settings_Fields', 'init' ) );
 
diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php
index b789b621b..1118df634 100644
--- a/includes/class-activitypub.php
+++ b/includes/class-activitypub.php
@@ -49,7 +49,8 @@ public static function init() {
 
 		\add_action( 'updated_postmeta', array( self::class, 'updated_postmeta' ), 10, 4 );
 		\add_action( 'added_post_meta', array( self::class, 'updated_postmeta' ), 10, 4 );
-		\add_filter( 'pre_option_activitypub_actor_mode', array( self::class, 'pre_get_option' ) );
+		\add_filter( 'pre_option_activitypub_actor_mode', array( self::class, 'pre_option_activitypub_actor_mode' ) );
+		\add_filter( 'pre_option_activitypub_authorized_fetch', array( self::class, 'pre_option_activitypub_authorized_fetch' ) );
 
 		\add_action( 'init', array( self::class, 'register_user_meta' ), 11 );
 
@@ -397,7 +398,7 @@ public static function pre_get_avatar_data( $args, $id_or_email ) {
 	 *
 	 * @return string|false The actor mode or false if it should not be filtered.
 	 */
-	public static function pre_get_option( $pre ) {
+	public static function pre_option_activitypub_actor_mode( $pre ) {
 		if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) {
 			return ACTIVITYPUB_BLOG_MODE;
 		}
@@ -413,6 +414,25 @@ public static function pre_get_option( $pre ) {
 		return $pre;
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
+		if ( ! \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			return $pre;
+		}
+
+		if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
+			return '1';
+		}
+
+		return '0';
+	}
+
 	/**
 	 * Store permalink in meta, to send delete Activity.
 	 *
@@ -832,6 +852,18 @@ public static function register_user_meta() {
 				'sanitize_callback' => 'absint',
 			)
 		);
+
+		\register_meta(
+			'user',
+			'activitypub_show_advanced_tab',
+			array(
+				'type'              => 'integer',
+				'description'       => 'Whether to show the advanced tab.',
+				'single'            => true,
+				'default'           => 0,
+				'sanitize_callback' => 'absint',
+			)
+		);
 	}
 
 	/**
diff --git a/includes/functions.php b/includes/functions.php
index 08dcef4da..c5e5ee92c 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -1414,14 +1414,7 @@ function get_upload_baseurl() {
  * @return boolean True if Authorized-Fetch is enabled, false otherwise.
  */
 function use_authorized_fetch() {
-	$use = false;
-
-	// Prefer the constant over the option.
-	if ( \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
-		$use = ACTIVITYPUB_AUTHORIZED_FETCH;
-	} else {
-		$use = (bool) \get_option( 'activitypub_authorized_fetch', '0' );
-	}
+	$use = (bool) \get_option( 'activitypub_authorized_fetch' );
 
 	/**
 	 * Filters whether to use Authorized-Fetch.
diff --git a/includes/wp-admin/class-advanced-settings-fields.php b/includes/wp-admin/class-advanced-settings-fields.php
new file mode 100644
index 000000000..9f1051ecb
--- /dev/null
+++ b/includes/wp-admin/class-advanced-settings-fields.php
@@ -0,0 +1,113 @@
+<?php
+/**
+ * Advanced Settings Fields file.
+ *
+ * @package ActivityPub
+ */
+
+namespace ActivityPub\WP_Admin;
+
+/**
+ * Advanced Settings Fields class.
+ */
+class Advanced_Settings_Fields {
+
+	/**
+	 * Initialize.
+	 */
+	public static function init() {
+		\add_action( 'load-settings_page_activitypub', array( self::class, 'register_advanced_fields' ) );
+	}
+
+	/**
+	 * Register settings.
+	 */
+	public static function register_advanced_fields() {
+		\add_settings_section(
+			'activitypub_advanced_settings',
+			\__( 'Advanced Settings', 'activitypub' ),
+			array( self::class, 'render_advanced_settings_section' ),
+			'activitypub_advanced_settings'
+		);
+
+		\add_settings_field(
+			'activitypub_outbox_purge_days',
+			\__( 'Outbox Retention Period', 'activitypub' ),
+			array( self::class, 'render_outbox_purge_days_field' ),
+			'activitypub_advanced_settings',
+			'activitypub_advanced_settings',
+			array( 'label_for' => 'activitypub_outbox_purge_days' )
+		);
+
+		if ( ! defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			\add_settings_field(
+				'activitypub_authorized_fetch',
+				\__( 'Authorized Fetch', 'activitypub' ),
+				array( self::class, 'render_authorized_fetch_field' ),
+				'activitypub_advanced_settings',
+				'activitypub_advanced_settings',
+				array( 'label_for' => 'activitypub_authorized_fetch' )
+			);
+		}
+	}
+
+	/**
+	 * Render Advanced Settings Section.
+	 */
+	public static function render_advanced_settings_section() {
+		?>
+		<p>
+			<?php
+			$allowed_html = array(
+				'a' => array(
+					'href'   => true,
+					'target' => true,
+				),
+			);
+			echo \wp_kses( \__( 'Advanced settings allow deep customization but can affect your site&#8217;s functionality, security, or performance if misconfigured. Only proceed if you fully understand the changes, and always back up your site beforehand. If unsure, consult <a href="https://github.com/Automattic/wordpress-activitypub/tree/trunk/docs" target="_blank">documentation</a> or seek <a href="https://wordpress.org/support/plugin/activitypub/" target="_blank">expert advice</a>.', 'activitypub' ), $allowed_html );
+			?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Render outbox purge days field.
+	 */
+	public static function render_outbox_purge_days_field() {
+		$value = \get_option( 'activitypub_outbox_purge_days', 180 );
+		echo '<input type="number" id="activitypub_outbox_purge_days" name="activitypub_outbox_purge_days" value="' . esc_attr( $value ) . '" class="small-text" min="0" max="365" />';
+		echo '<p class="description">' . \wp_kses(
+			sprintf(
+				// translators: 1: Definition of Outbox; 2: Default value (180).
+				\__( 'Maximum number of days to keep items in the <abbr title="%1$s">Outbox</abbr>. A lower value might be better for sites with lots of activity to maintain site performance. Default: <code>%2$s</code>', 'activitypub' ),
+				\esc_attr__( 'A virtual location on a user&#8217;s profile where all the activities (posts, likes, replies) they publish are stored, acting as a feed that other users can access to see their publicly shared content', 'activitypub' ),
+				\esc_html( 180 )
+			),
+			array(
+				'abbr' => array( 'title' => array() ),
+				'code' => array(),
+			)
+		) . '</p>';
+	}
+
+	/**
+	 * Render use Authorized Fetch field.
+	 */
+	public static function render_authorized_fetch_field() {
+		$value = \get_option( 'activitypub_authorized_fetch', '1' );
+		?>
+		<p>
+			<label>
+				<input type="checkbox" id="activitypub_authorized_fetch" name="activitypub_authorized_fetch" value="1" <?php checked( '1', $value ); ?> />
+				<?php \esc_html_e( 'Require HTTP signature authentication on ActivityPub representations of public posts and profiles.', 'activitypub' ); ?>
+			</label>
+		</p>
+		<p class="description">
+			<?php \esc_html_e( '⚠ Secure mode has its limitations, which is why it is not enabled by default. It is not fully supported by all software in the fediverse, and some features may break, especially when interacting with Mastodon servers older than version 3.0. Additionally, since it requires authentication for public content, caching is not possible, leading to higher computational costs.', 'activitypub' ); ?>
+		</p>
+		<p class="description">
+			<?php \esc_html_e( '⚠ Secure mode does not hide the HTML representations of public posts and profiles. While HTML is a less consistent format (that potentially changes often) compared to first-class ActivityPub representations or the REST API, it still poses a potential risk for content scraping.', 'activitypub' ); ?>
+		</p>
+		<?php
+	}
+}
diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php
index aced3756a..40ff25042 100644
--- a/includes/wp-admin/class-settings-fields.php
+++ b/includes/wp-admin/class-settings-fields.php
@@ -155,33 +155,6 @@ public static function register_settings_fields() {
 			'activitypub_server',
 			array( 'label_for' => 'activitypub_relays' )
 		);
-
-		add_settings_field(
-			'activitypub_outbox_purge_days',
-			__( 'Outbox Retention Period', 'activitypub' ),
-			array( self::class, 'render_outbox_purge_days_field' ),
-			'activitypub_settings',
-			'activitypub_server',
-			array( 'label_for' => 'activitypub_outbox_purge_days' )
-		);
-
-		if ( ! defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
-			add_settings_section(
-				'activitypub_security',
-				__( 'Security', 'activitypub' ),
-				'__return_empty_string',
-				'activitypub_settings'
-			);
-
-			add_settings_field(
-				'activitypub_authorized_fetch',
-				__( 'Authorized Fetch', 'activitypub' ),
-				array( self::class, 'render_authorized_fetch_field' ),
-				'activitypub_settings',
-				'activitypub_security',
-				array( 'label_for' => 'activitypub_authorized_fetch' )
-			);
-		}
 	}
 
 	/**
@@ -398,6 +371,36 @@ public static function render_support_post_types_field() {
 		<?php
 	}
 
+	/**
+	 * Render allow interactions field.
+	 */
+	public static function render_allow_interactions_field() {
+		if ( defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) && ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
+			echo '<p class="description">' . \esc_html__( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator.', 'activitypub' ) . '</p>';
+			return;
+		}
+
+		$allow_likes   = get_option( 'activitypub_allow_likes', '1' );
+		$allow_reposts = get_option( 'activitypub_allow_reposts', '1' );
+		?>
+		<fieldset>
+			<p>
+				<label>
+					<input type="checkbox" name="activitypub_allow_likes" value="1" <?php checked( '1', $allow_likes ); ?> />
+					<?php esc_html_e( 'Receive likes', 'activitypub' ); ?>
+				</label>
+			</p>
+			<p>
+				<label>
+					<input type="checkbox" name="activitypub_allow_announces" value="1" <?php checked( '1', $allow_reposts ); ?> />
+					<?php esc_html_e( 'Receive reblogs (boosts)', 'activitypub' ); ?>
+				</label>
+			</p>
+			<p class="description"><?php esc_html_e( 'Types of interactions from the Fediverse your blog should accept.', 'activitypub' ); ?></p>
+		</fieldset>
+		<?php
+	}
+
 	/**
 	 * Render use hashtags field.
 	 */
@@ -445,102 +448,11 @@ class="large-text"
 		<?php
 	}
 
-	/**
-	 * Render blocklist field.
-	 */
-	public static function render_blocklist_field() {
-		?>
-		<p>
-			<?php
-			echo wp_kses(
-				sprintf(
-					// translators: %s is a URL.
-					__( 'To block servers, add the host of the server to the "<a href="%s">Disallowed Comment Keys</a>" list.', 'activitypub' ),
-					esc_url( admin_url( 'options-discussion.php#disallowed_keys' ) )
-				),
-				'default'
-			);
-			?>
-		</p>
-		<?php
-	}
-
-	/**
-	 * Render outbox purge days field.
-	 */
-	public static function render_outbox_purge_days_field() {
-		$value = get_option( 'activitypub_outbox_purge_days', 180 );
-		echo '<input type="number" id="activitypub_outbox_purge_days" name="activitypub_outbox_purge_days" value="' . esc_attr( $value ) . '" class="small-text" min="0" max="365" />';
-		echo '<p class="description">' . wp_kses(
-			sprintf(
-				// translators: 1: Definition of Outbox; 2: Default value (180).
-				__( 'Maximum number of days to keep items in the <abbr title="%1$s">Outbox</abbr>. A lower value might be better for sites with lots of activity to maintain site performance. Default: <code>%2$s</code>', 'activitypub' ),
-				esc_attr__( 'A virtual location on a user&#8217;s profile where all the activities (posts, likes, replies) they publish are stored, acting as a feed that other users can access to see their publicly shared content', 'activitypub' ),
-				esc_html( 180 )
-			),
-			array(
-				'abbr' => array( 'title' => array() ),
-				'code' => array(),
-			)
-		) . '</p>';
-	}
-
-	/**
-	 * Render allow interactions field.
-	 */
-	public static function render_allow_interactions_field() {
-		if ( defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) && ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
-			echo '<p class="description">' . \esc_html__( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator.', 'activitypub' ) . '</p>';
-			return;
-		}
-
-		$allow_likes   = get_option( 'activitypub_allow_likes', '1' );
-		$allow_reposts = get_option( 'activitypub_allow_reposts', '1' );
-		?>
-		<fieldset>
-			<p>
-				<label>
-					<input type="checkbox" name="activitypub_allow_likes" value="1" <?php checked( '1', $allow_likes ); ?> />
-					<?php esc_html_e( 'Receive likes', 'activitypub' ); ?>
-				</label>
-			</p>
-			<p>
-				<label>
-					<input type="checkbox" name="activitypub_allow_reposts" value="1" <?php checked( '1', $allow_reposts ); ?> />
-					<?php esc_html_e( 'Receive reblogs (boosts)', 'activitypub' ); ?>
-				</label>
-			</p>
-			<p class="description"><?php esc_html_e( 'Types of interactions from the Fediverse your blog should accept.', 'activitypub' ); ?></p>
-		</fieldset>
-		<?php
-	}
-
-	/**
-	 * Render authorized fetch field.
-	 */
-	public static function render_authorized_fetch_field() {
-		$value = get_option( 'activitypub_authorized_fetch', '0' );
-		?>
-		<p>
-			<label>
-				<input type="checkbox" id="activitypub_authorized_fetch" name="activitypub_authorized_fetch" value="1" <?php checked( '1', $value ); ?> />
-				<?php esc_html_e( 'Require HTTP signature authentication on ActivityPub representations of public posts and profiles.', 'activitypub' ); ?>
-			</label>
-		</p>
-		<p class="description">
-			<?php \esc_html_e( '⚠ Secure mode has its limitations, which is why it is not enabled by default. It is not fully supported by all software in the fediverse, and some features may break, especially when interacting with Mastodon servers older than version 3.0. Additionally, since it requires authentication for public content, caching is not possible, leading to higher computational costs.', 'activitypub' ); ?>
-		</p>
-		<p class="description">
-			<?php \esc_html_e( '⚠ Secure mode does not hide the HTML representations of public posts and profiles. While HTML is a less consistent format (that potentially changes often) compared to first-class ActivityPub representations or the REST API, it still poses a potential risk for content scraping.', 'activitypub' ); ?>
-		</p>
-		<?php
-	}
-
 	/**
 	 * Render relays field.
 	 */
 	public static function render_relays_field() {
-		$value = get_option( 'activitypub_relays', array() );
+		$value = \get_option( 'activitypub_relays', array() );
 		?>
 		<textarea
 			id="activitypub_relays"
@@ -548,21 +460,41 @@ public static function render_relays_field() {
 			class="large-text"
 			cols="50"
 			rows="5"
-		><?php echo esc_textarea( implode( PHP_EOL, $value ) ); ?></textarea>
+		><?php echo \esc_textarea( implode( PHP_EOL, $value ) ); ?></textarea>
 		<p class="description">
-			<?php echo wp_kses( __( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
+			<?php echo \wp_kses( \__( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
 		</p>
 		<p class="description">
 			<?php
-			echo wp_kses(
-				__( 'Enter the <strong>Inbox-URLs</strong> (e.g. <code>https://relay.example.com/inbox</code>) of the relays you want to use, one per line.', 'activitypub' ),
+			echo \wp_kses(
+				\__( 'Enter the <strong>Inbox-URLs</strong> (e.g. <code>https://relay.example.com/inbox</code>) of the relays you want to use, one per line.', 'activitypub' ),
 				array(
 					'strong' => array(),
 					'code'   => array(),
 				)
 			);
 			?>
-			<?php echo wp_kses( __( 'You can find a list of public relays on <a href="https://relaylist.com/" target="_blank">relaylist.com</a> or on <a href="https://fedidb.org/software/activity-relay" target="_blank">FediDB</a>.', 'activitypub' ), 'default' ); ?>
+			<?php echo \wp_kses( \__( 'You can find a list of public relays on <a href="https://relaylist.com/" target="_blank">relaylist.com</a> or on <a href="https://fedidb.org/software/activity-relay" target="_blank">FediDB</a>.', 'activitypub' ), 'default' ); ?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Render blocklist field.
+	 */
+	public static function render_blocklist_field() {
+		?>
+		<p>
+			<?php
+			echo \wp_kses(
+				\sprintf(
+					// translators: %s is a URL.
+					\__( 'To block servers, add the host of the server to the "<a href="%s">Disallowed Comment Keys</a>" list.', 'activitypub' ),
+					\esc_url( \admin_url( 'options-discussion.php#disallowed_keys' ) )
+				),
+				'default'
+			);
+			?>
 		</p>
 		<?php
 	}
diff --git a/includes/wp-admin/class-settings.php b/includes/wp-admin/class-settings.php
index ddeaa8ae0..5e1793a2a 100644
--- a/includes/wp-admin/class-settings.php
+++ b/includes/wp-admin/class-settings.php
@@ -68,16 +68,6 @@ public static function register_settings() {
 			)
 		);
 
-		\register_setting(
-			'activitypub',
-			'activitypub_outbox_purge_days',
-			array(
-				'type'        => 'integer',
-				'description' => \__( 'Number of days to keep items in the Outbox.', 'activitypub' ),
-				'default'     => 180,
-			)
-		);
-
 		\register_setting(
 			'activitypub',
 			'activitypub_object_type',
@@ -169,31 +159,31 @@ public static function register_settings() {
 
 		\register_setting(
 			'activitypub',
-			'activitypub_authorized_fetch',
+			'activitypub_mailer_new_dm',
 			array(
 				'type'        => 'boolean',
-				'description' => \__( 'Require HTTP signature authentication.', 'activitypub' ),
-				'default'     => false,
+				'description' => \__( 'Send notifications via e-mail when a direct message is received.', 'activitypub' ),
+				'default'     => '0',
 			)
 		);
 
 		\register_setting(
-			'activitypub',
-			'activitypub_mailer_new_follower',
+			'activitypub_advanced',
+			'activitypub_outbox_purge_days',
 			array(
-				'type'        => 'boolean',
-				'description' => \__( 'Send notifications via e-mail when a new follower is added.', 'activitypub' ),
-				'default'     => '0',
+				'type'        => 'integer',
+				'description' => \__( 'Number of days to keep items in the Outbox.', 'activitypub' ),
+				'default'     => 180,
 			)
 		);
 
 		\register_setting(
-			'activitypub',
-			'activitypub_mailer_new_dm',
+			'activitypub_advanced',
+			'activitypub_authorized_fetch',
 			array(
 				'type'        => 'boolean',
-				'description' => \__( 'Send notifications via e-mail when a direct message is received.', 'activitypub' ),
-				'default'     => '0',
+				'description' => \__( 'Require HTTP signature authentication.', 'activitypub' ),
+				'default'     => false,
 			)
 		);
 
@@ -258,8 +248,9 @@ public static function register_settings() {
 	 * Load settings page.
 	 */
 	public static function settings_page() {
-		$show_welcome_tab = \get_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', true );
-		$default_tab      = $show_welcome_tab ? 'welcome' : 'settings';
+		$show_welcome_tab  = \get_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', true );
+		$show_advanced_tab = \get_user_meta( \get_current_user_id(), 'activitypub_show_advanced_tab', true );
+		$default_tab       = $show_welcome_tab ? 'welcome' : 'settings';
 		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
 		$tab = isset( $_GET['tab'] ) ? \sanitize_key( $_GET['tab'] ) : $default_tab;
 
@@ -282,6 +273,14 @@ public static function settings_page() {
 			'template' => ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php',
 		);
 
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+		if ( ( isset( $_GET['tab'] ) && 'advanced' === $_GET['tab'] ) || $show_advanced_tab ) {
+			$settings_tabs['advanced'] = array(
+				'label'    => \__( 'Advanced', 'activitypub' ),
+				'template' => ACTIVITYPUB_PLUGIN_DIR . 'templates/advanced-settings.php',
+			);
+		}
+
 		if ( ! is_user_disabled( Actors::BLOG_USER_ID ) ) {
 			$settings_tabs['blog-profile'] = array(
 				'label'    => __( 'Blog Profile', 'activitypub' ),
@@ -492,26 +491,41 @@ public static function add_screen_option( $screen_settings, $screen ) {
 			return $screen_settings;
 		}
 
-		if ( isset( $_POST['activitypub_show_welcome_tab'] ) && isset( $_POST['screenoptionnonce'] ) ) {
-			$nonce   = \sanitize_text_field( \wp_unslash( $_POST['screenoptionnonce'] ) );
-			$welcome = \sanitize_text_field( \wp_unslash( $_POST['activitypub_show_welcome_tab'] ) );
-			// Verify screen options nonce.
-			if ( \wp_verify_nonce( $nonce, 'screen-options-nonce' ) ) {
-				$welcome_checked = empty( $welcome ) ? 0 : 1;
-				\update_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', $welcome_checked );
+		// Verify screen options nonce.
+		if ( isset( $_POST['screenoptionnonce'] ) ) {
+			$nonce = \sanitize_text_field( \wp_unslash( $_POST['screenoptionnonce'] ) );
+			if ( ! \wp_verify_nonce( $nonce, 'screen-options-nonce' ) ) {
+				return $screen_settings;
 			}
 		}
 
+		if ( isset( $_POST['activitypub_show_welcome_tab'] ) ) {
+			$welcome         = \sanitize_text_field( \wp_unslash( $_POST['activitypub_show_welcome_tab'] ) );
+			$welcome_checked = empty( $welcome ) ? 0 : 1;
+			\update_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', $welcome_checked );
+		}
+
+		if ( isset( $_POST['activitypub_show_advanced_tab'] ) ) {
+			$advanced_settings         = \sanitize_text_field( \wp_unslash( $_POST['activitypub_show_advanced_tab'] ) );
+			$advanced_settings_checked = empty( $advanced_settings ) ? 0 : 1;
+			\update_user_meta( \get_current_user_id(), 'activitypub_show_advanced_tab', $advanced_settings_checked );
+		}
+
 		$screen_settings = '<fieldset>
 		<legend class="screen-layout">' . \esc_html__( 'Settings Pages', 'activitypub' ) . '</legend>
 		<p>
-			' . \__( 'Some settings pages can be shown or hidden by using the checkboxes.', 'activitypub' ) . '
+			' . \esc_html__( 'Some settings pages can be shown or hidden by using the checkboxes.', 'activitypub' ) . '
 		</p>
 		<div class="metabox-prefs-container">
 			<label for="activitypub_show_welcome_tab">
 				<input name="activitypub_show_welcome_tab" type="hidden" value="0" />
 				<input name="activitypub_show_welcome_tab" type="checkbox" id="activitypub_show_welcome_tab" value="1" ' . \checked( 1, \get_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', true ), false ) . ' />
-				' . \__( 'Welcome Page', 'activitypub' ) . '
+				' . \esc_html__( 'Welcome Page', 'activitypub' ) . '
+			</label>
+			<label for="activitypub_show_advanced_tab">
+				<input name="activitypub_show_advanced_tab" type="hidden" value="0" />
+				<input name="activitypub_show_advanced_tab" type="checkbox" id="activitypub_show_advanced_tab" value="1" ' . \checked( 1, \get_user_meta( \get_current_user_id(), 'activitypub_show_advanced_tab', true ), false ) . ' />
+				' . \esc_html__( 'Advanced Settings', 'activitypub' ) . '
 			</label>
 		</div>
 	</fieldset>';
diff --git a/includes/wp-admin/class-welcome-fields.php b/includes/wp-admin/class-welcome-fields.php
index 6a23038b0..25953dfed 100644
--- a/includes/wp-admin/class-welcome-fields.php
+++ b/includes/wp-admin/class-welcome-fields.php
@@ -150,7 +150,7 @@ public static function render_blog_profile_section() {
 		<p>
 			<?php \esc_html_e( 'This blog profile will federate all posts written on your blog, regardless of the author who posted it.', 'activitypub' ); ?>
 			<a href="<?php echo \esc_url( \admin_url( '/options-general.php?page=activitypub&tab=blog-profile' ) ); ?>">
-				<?php \esc_html_e( 'Customize the blog profile', 'activitypub' ); ?>
+				<?php \esc_html_e( 'Customize the blog profile.', 'activitypub' ); ?>
 			</a>
 		</p>
 		<?php
diff --git a/templates/advanced-settings.php b/templates/advanced-settings.php
new file mode 100644
index 000000000..47c1e84c1
--- /dev/null
+++ b/templates/advanced-settings.php
@@ -0,0 +1,16 @@
+<?php
+/**
+ * ActivityPub settings template.
+ *
+ * @package Activitypub
+ */
+
+?>
+
+<div class="activitypub-settings activitypub-settings-page hide-if-no-js">
+	<form method="post" action="options.php">
+		<?php \settings_fields( 'activitypub_advanced' ); ?>
+		<?php \do_settings_sections( 'activitypub_advanced_settings' ); ?>
+		<?php \submit_button(); ?>
+	</form>
+</div>


Reviews:
user: login: obenland
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-03-27T14:13:17Z
user: login: pfefferle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-28T11:09:50Z
user: login: pfefferle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T14:22:14Z
user: login: obenland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:22:36Z
user: login: obenland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:23:24Z
user: login: pfefferle
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T15:45:41Z
user: login: obenland
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-02T16:57:43Z
user: login: obenland
body: I'd still move `pre_option_activitypub_authorized_fetch` but is probably not a blocker
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-04-02T17:01:23Z
user: login: obenland
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-02T19:38:58Z

Review comments:
diff_hunk: @@ -897,4 +901,14 @@ public static function migrate_actor_mode() {
 			\update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
 		}
 	}
+
+	/**
+	 * Migrate the advanced settings.
+	 */
+	public static function migrate_advanced_settings() {
+		// Check if one of the advanced settings is set and show the tab.
+		if ( '1' === \get_option( 'activitypub_authorized_fetch' ) || '180' !== \get_option( 'activitypub_outbox_purge_days' ) ) {
+			\update_option( 'activitypub_show_advanced_tab', '1' );
path: includes/class-migration.php
user: login: obenland
body: Could we make this a user meta, too, like for the welcome screen? Otherwise this will be enabled/disabled for every user on the site.
created_at: 2025-03-27T14:07:03Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+<?php
+/**
+ * Advanced Settings Fields file.
+ *
+ * @package ActivityPub
+ */
+
+namespace ActivityPub\WP_Admin;
+
+/**
+ * Advanced Settings Fields class.
+ */
+class Advanced_Settings_Fields {
+
+	/**
+	 * Initialize.
+	 */
+	public static function init() {
+		add_action( 'load-settings_page_activitypub', array( self::class, 'register_advanced_fields' ) );
+	}
+
+	/**
+	 * Register settings.
+	 */
+	public static function register_advanced_fields() {
+		\add_settings_section(
path: includes/wp-admin/class-advanced-settings-fields.php
user: login: obenland
body: Not sure if we've discussed this yet, but I think it would be preferable to have this as a section in the regular settings tab. We have limited horizontal space in the navigation, but unlimited vertical space.
created_at: 2025-03-27T14:08:21Z
author_association: MEMBER
diff_hunk: @@ -528,4 +440,24 @@ class="large-text"
 		</p>
 		<?php
 	}
+
+	/**
+	 * Render blocklist field.
+	 */
+	public static function render_blocklist_field() {
+		?>
+		<p>
+			<?php
+			echo wp_kses(
+				sprintf(
+					// translators: %s is a URL.
+					__( 'To block servers, add the host of the server to the "<a href="%s">Disallowed Comment Keys</a>" list.', 'activitypub' ),
+					esc_url( admin_url( 'options-discussion.php#disallowed_keys' ) )
path: includes/wp-admin/class-settings-fields.php
user: login: obenland
body: There are some missing backslashes here and in other files
created_at: 2025-03-27T14:10:30Z
author_association: MEMBER
diff_hunk: @@ -491,6 +518,11 @@ public static function add_screen_option( $screen_settings, $screen ) {
 				<input name="activitypub_show_welcome_tab" type="checkbox" id="activitypub_show_welcome_tab" value="1" ' . \checked( 1, \get_user_meta( \get_current_user_id(), 'activitypub_show_welcome_tab', true ), false ) . ' />
 				' . \__( 'Welcome Page', 'activitypub' ) . '
 			</label>
+			<label for="activitypub_show_advanced_tab">
+				<input name="activitypub_show_advanced_tab" type="hidden" value="0" />
+				<input name="activitypub_show_advanced_tab" type="checkbox" id="activitypub_show_advanced_tab" value="1" ' . \checked( 1, \get_option( 'activitypub_show_advanced_tab' ), false ) . ' />
+				' . \__( 'Advanced Settings', 'activitypub' ) . '
path: includes/wp-admin/class-settings.php
user: login: obenland
body: ```suggestion
				' . \esc_html__( 'Advanced Settings', 'activitypub' ) . '
```
This should also be added in line 513 and 519.
created_at: 2025-03-27T14:11:50Z
author_association: MEMBER
diff_hunk: @@ -16,6 +16,10 @@
 	<nav class="activitypub-settings-tabs-wrapper" aria-label="<?php \esc_attr_e( 'Secondary menu', 'activitypub' ); ?>">
 		<?php
 		foreach ( $args['tabs'] as $slug => $label ) :
+			if ( isset( $args[ $slug ]['visible'] ) && ! $args[ $slug ]['visible'] ) {
path: templates/admin-header.php
user: login: obenland
body: I couldn't find any other reference to `visible`, is this outdated?
created_at: 2025-03-27T14:13:06Z
author_association: MEMBER
diff_hunk: @@ -897,4 +901,14 @@ public static function migrate_actor_mode() {
 			\update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
 		}
 	}
+
+	/**
+	 * Migrate the advanced settings.
+	 */
+	public static function migrate_advanced_settings() {
+		// Check if one of the advanced settings is set and show the tab.
+		if ( '1' === \get_option( 'activitypub_authorized_fetch' ) || '180' !== \get_option( 'activitypub_outbox_purge_days' ) ) {
+			\update_option( 'activitypub_show_advanced_tab', '1' );
path: includes/class-migration.php
user: login: pfefferle
body: It is limited to the admins, but yes, I can change it.
created_at: 2025-03-28T11:09:50Z
author_association: MEMBER
diff_hunk: @@ -897,4 +901,14 @@ public static function migrate_actor_mode() {
 			\update_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
 		}
 	}
+
+	/**
+	 * Migrate the advanced settings.
+	 */
+	public static function migrate_advanced_settings() {
+		// Check if one of the advanced settings is set and show the tab.
+		if ( '1' === \get_option( 'activitypub_authorized_fetch' ) || '180' !== \get_option( 'activitypub_outbox_purge_days' ) ) {
+			\update_option( 'activitypub_show_advanced_tab', '1' );
path: includes/class-migration.php
user: login: pfefferle
body: @obenland done!
created_at: 2025-04-02T14:22:14Z
author_association: MEMBER
diff_hunk: @@ -498,6 +499,25 @@ public static function theme_compat() {
 		}
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
+		if ( ! \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			return $pre;
+		}
+
+		if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
+			return '1';
+		}
+
+		return '0';
path: includes/class-activitypub.php
user: login: obenland
body: ```suggestion
		return (bool) ACTIVITYPUB_AUTHORIZED_FETCH;
```
Given the type cast in `use_authorized_fetch` we're only interested in true or false here?
created_at: 2025-04-02T15:04:26Z
author_association: MEMBER
diff_hunk: @@ -498,6 +499,25 @@ public static function theme_compat() {
 		}
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
path: includes/class-activitypub.php
user: login: obenland
body: Maybe this could be moved underneath `pre_option_activitypub_actor_mode` to group them and keep with the order the callbacks are registered?
created_at: 2025-04-02T15:05:12Z
author_association: MEMBER
diff_hunk: @@ -1414,14 +1414,7 @@ function get_upload_baseurl() {
  * @return boolean True if Authorized-Fetch is enabled, false otherwise.
  */
 function use_authorized_fetch() {
-	$use = false;
-
-	// Prefer the constant over the option.
-	if ( \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
-		$use = ACTIVITYPUB_AUTHORIZED_FETCH;
-	} else {
-		$use = (bool) \get_option( 'activitypub_authorized_fetch', '0' );
-	}
+	$use = (bool) \get_option( 'activitypub_authorized_fetch', '0' );
path: includes/functions.php
user: login: obenland
body: ```suggestion
	$use = (bool) \get_option( 'activitypub_authorized_fetch' );
```
Since false is fine here the default is fine.
created_at: 2025-04-02T15:06:18Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+<?php
+/**
+ * Advanced Settings Fields file.
+ *
+ * @package ActivityPub
+ */
+
+namespace ActivityPub\WP_Admin;
+
+/**
+ * Advanced Settings Fields class.
+ */
+class Advanced_Settings_Fields {
+
+	/**
+	 * Initialize.
+	 */
+	public static function init() {
+		add_action( 'load-settings_page_activitypub', array( self::class, 'register_advanced_fields' ) );
path: includes/wp-admin/class-advanced-settings-fields.php
user: login: obenland
body: There's an opportunity to prefix function calls with backslashes here and in later function calls.
created_at: 2025-04-02T15:08:07Z
author_association: MEMBER
diff_hunk: @@ -445,124 +448,53 @@ class="large-text"
 		<?php
 	}
 
-	/**
-	 * Render blocklist field.
-	 */
-	public static function render_blocklist_field() {
-		?>
-		<p>
-			<?php
-			echo wp_kses(
-				sprintf(
-					// translators: %s is a URL.
-					__( 'To block servers, add the host of the server to the "<a href="%s">Disallowed Comment Keys</a>" list.', 'activitypub' ),
-					esc_url( admin_url( 'options-discussion.php#disallowed_keys' ) )
-				),
-				'default'
-			);
-			?>
-		</p>
-		<?php
-	}
-
-	/**
-	 * Render outbox purge days field.
-	 */
-	public static function render_outbox_purge_days_field() {
-		$value = get_option( 'activitypub_outbox_purge_days', 180 );
-		echo '<input type="number" id="activitypub_outbox_purge_days" name="activitypub_outbox_purge_days" value="' . esc_attr( $value ) . '" class="small-text" min="0" max="365" />';
-		echo '<p class="description">' . wp_kses(
-			sprintf(
-				// translators: 1: Definition of Outbox; 2: Default value (180).
-				__( 'Maximum number of days to keep items in the <abbr title="%1$s">Outbox</abbr>. A lower value might be better for sites with lots of activity to maintain site performance. Default: <code>%2$s</code>', 'activitypub' ),
-				esc_attr__( 'A virtual location on a user&#8217;s profile where all the activities (posts, likes, replies) they publish are stored, acting as a feed that other users can access to see their publicly shared content', 'activitypub' ),
-				esc_html( 180 )
-			),
-			array(
-				'abbr' => array( 'title' => array() ),
-				'code' => array(),
-			)
-		) . '</p>';
-	}
-
-	/**
-	 * Render allow interactions field.
-	 */
-	public static function render_allow_interactions_field() {
-		if ( defined( 'ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS' ) && ACTIVITYPUB_DISABLE_INCOMING_INTERACTIONS ) {
-			echo '<p class="description">' . \esc_html__( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator.', 'activitypub' ) . '</p>';
-			return;
-		}
-
-		$allow_likes   = get_option( 'activitypub_allow_likes', '1' );
-		$allow_reposts = get_option( 'activitypub_allow_reposts', '1' );
-		?>
-		<fieldset>
-			<p>
-				<label>
-					<input type="checkbox" name="activitypub_allow_likes" value="1" <?php checked( '1', $allow_likes ); ?> />
-					<?php esc_html_e( 'Receive likes', 'activitypub' ); ?>
-				</label>
-			</p>
-			<p>
-				<label>
-					<input type="checkbox" name="activitypub_allow_reposts" value="1" <?php checked( '1', $allow_reposts ); ?> />
-					<?php esc_html_e( 'Receive reblogs (boosts)', 'activitypub' ); ?>
-				</label>
-			</p>
-			<p class="description"><?php esc_html_e( 'Types of interactions from the Fediverse your blog should accept.', 'activitypub' ); ?></p>
-		</fieldset>
-		<?php
-	}
-
-	/**
-	 * Render authorized fetch field.
-	 */
-	public static function render_authorized_fetch_field() {
-		$value = get_option( 'activitypub_authorized_fetch', '0' );
-		?>
-		<p>
-			<label>
-				<input type="checkbox" id="activitypub_authorized_fetch" name="activitypub_authorized_fetch" value="1" <?php checked( '1', $value ); ?> />
-				<?php esc_html_e( 'Require HTTP signature authentication on ActivityPub representations of public posts and profiles.', 'activitypub' ); ?>
-			</label>
-		</p>
-		<p class="description">
-			<?php \esc_html_e( '⚠ Secure mode has its limitations, which is why it is not enabled by default. It is not fully supported by all software in the fediverse, and some features may break, especially when interacting with Mastodon servers older than version 3.0. Additionally, since it requires authentication for public content, caching is not possible, leading to higher computational costs.', 'activitypub' ); ?>
-		</p>
-		<p class="description">
-			<?php \esc_html_e( '⚠ Secure mode does not hide the HTML representations of public posts and profiles. While HTML is a less consistent format (that potentially changes often) compared to first-class ActivityPub representations or the REST API, it still poses a potential risk for content scraping.', 'activitypub' ); ?>
-		</p>
-		<?php
-	}
-
 	/**
 	 * Render relays field.
 	 */
 	public static function render_relays_field() {
-		$value = get_option( 'activitypub_relays', array() );
+		$value = \get_option( 'activitypub_relays', array() );
 		?>
 		<textarea
 			id="activitypub_relays"
 			name="activitypub_relays"
 			class="large-text"
 			cols="50"
 			rows="5"
-		><?php echo esc_textarea( implode( PHP_EOL, $value ) ); ?></textarea>
+		><?php echo \esc_textarea( implode( PHP_EOL, $value ) ); ?></textarea>
 		<p class="description">
-			<?php echo wp_kses( __( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
+			<?php echo \wp_kses( __( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
path: includes/wp-admin/class-settings-fields.php
user: login: obenland
body: ```suggestion
			<?php echo \wp_kses( \__( 'A <strong>Fediverse-Relay</strong> distributes content across instances, expanding reach, engagement, and discoverability, especially for smaller instances.', 'activitypub' ), 'default' ); ?>
```
created_at: 2025-04-02T15:10:08Z
author_association: MEMBER
diff_hunk: @@ -282,6 +273,14 @@ public static function settings_page() {
 			'template' => ACTIVITYPUB_PLUGIN_DIR . 'templates/settings.php',
 		);
 
+		// phpcs:ignore WordPress.Security.NonceVerification.Recommended
+		if ( ( isset( $_GET['tab'] ) && 'advanced' === $_GET['tab'] ) || $show_advanced_tab ) {
+			$settings_tabs['advanced'] = array(
+				'label'    => __( 'Advanced', 'activitypub' ),
path: includes/wp-admin/class-settings.php
user: login: obenland
body: ```suggestion
				'label'    => \__( 'Advanced', 'activitypub' ),
```
created_at: 2025-04-02T15:10:52Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+<?php
+/**
+ * Advanced Settings Fields file.
+ *
+ * @package ActivityPub
+ */
+
+namespace ActivityPub\WP_Admin;
+
+/**
+ * Advanced Settings Fields class.
+ */
+class Advanced_Settings_Fields {
+
+	/**
+	 * Initialize.
+	 */
+	public static function init() {
+		add_action( 'load-settings_page_activitypub', array( self::class, 'register_advanced_fields' ) );
+	}
+
+	/**
+	 * Register settings.
+	 */
+	public static function register_advanced_fields() {
+		\add_settings_section(
+			'activitypub_advanced_settings',
+			\__( 'Advanced Settings', 'activitypub' ),
+			array( self::class, 'render_advanced_settings_section' ),
+			'activitypub_advanced_settings'
+		);
+
+		add_settings_field(
+			'activitypub_outbox_purge_days',
+			__( 'Outbox Retention Period', 'activitypub' ),
+			array( self::class, 'render_outbox_purge_days_field' ),
+			'activitypub_advanced_settings',
+			'activitypub_advanced_settings',
+			array( 'label_for' => 'activitypub_outbox_purge_days' )
+		);
+
+		\add_settings_field(
+			'activitypub_authorized_fetch',
+			__( 'Authorized Fetch', 'activitypub' ),
+			array( self::class, 'render_authorized_fetch_field' ),
+			'activitypub_advanced_settings',
+			'activitypub_advanced_settings',
+			array( 'label_for' => 'activitypub_authorized_fetch' )
+		);
+	}
+
+	/**
+	 * Render Advanced Settings Section.
+	 */
+	public static function render_advanced_settings_section() {
+		?>
+		<p>
+			<?php
+			$allowed_html = array(
+				'a' => array(
+					'href'   => true,
+					'target' => true,
+				),
+			);
+			echo wp_kses( __( 'Advanced settings allow deep customization but can affect your site&rsquo;s functionality, security, or performance if misconfigured. Only proceed if you fully understand the changes, and always back up your site beforehand. If unsure, consult <a href="https://github.com/Automattic/wordpress-activitypub/tree/trunk/docs" target="_blank">documentation</a> or seek <a href="https://wordpress.org/support/plugin/activitypub/" target="_blank">expert advice</a>.', 'activitypub' ), $allowed_html );
+			?>
+		</p>
+		<?php
+	}
+
+	/**
+	 * Render outbox purge days field.
+	 */
+	public static function render_outbox_purge_days_field() {
+		$value = get_option( 'activitypub_outbox_purge_days', 180 );
+		echo '<input type="number" id="activitypub_outbox_purge_days" name="activitypub_outbox_purge_days" value="' . esc_attr( $value ) . '" class="small-text" min="0" max="365" />';
+		echo '<p class="description">' . wp_kses(
+			sprintf(
+				// translators: 1: Definition of Outbox; 2: Default value (180).
+				__( 'Maximum number of days to keep items in the <abbr title="%1$s">Outbox</abbr>. A lower value might be better for sites with lots of activity to maintain site performance. Default: <code>%2$s</code>', 'activitypub' ),
+				esc_attr__( 'A virtual location on a user&#8217;s profile where all the activities (posts, likes, replies) they publish are stored, acting as a feed that other users can access to see their publicly shared content', 'activitypub' ),
+				esc_html( 180 )
+			),
+			array(
+				'abbr' => array( 'title' => array() ),
+				'code' => array(),
+			)
+		) . '</p>';
+	}
+
+	/**
+	 * Render use Authorized Fetch field.
+	 */
+	public static function render_authorized_fetch_field() {
+		$disabled = '';
+		if ( defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			$disabled = 'disabled';
path: includes/wp-admin/class-advanced-settings-fields.php
user: login: obenland
body: Instead of disabling it, should we replace it with the same message as we do for actor mode and the interactions setting?
created_at: 2025-04-02T15:14:15Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,115 @@
+<?php
+/**
+ * Advanced Settings Fields file.
+ *
+ * @package ActivityPub
+ */
+
+namespace ActivityPub\WP_Admin;
+
+/**
+ * Advanced Settings Fields class.
+ */
+class Advanced_Settings_Fields {
+
+	/**
+	 * Initialize.
+	 */
+	public static function init() {
+		add_action( 'load-settings_page_activitypub', array( self::class, 'register_advanced_fields' ) );
+	}
+
+	/**
+	 * Register settings.
+	 */
+	public static function register_advanced_fields() {
+		\add_settings_section(
+			'activitypub_advanced_settings',
+			\__( 'Advanced Settings', 'activitypub' ),
+			array( self::class, 'render_advanced_settings_section' ),
+			'activitypub_advanced_settings'
+		);
+
+		add_settings_field(
+			'activitypub_outbox_purge_days',
+			__( 'Outbox Retention Period', 'activitypub' ),
+			array( self::class, 'render_outbox_purge_days_field' ),
+			'activitypub_advanced_settings',
+			'activitypub_advanced_settings',
+			array( 'label_for' => 'activitypub_outbox_purge_days' )
+		);
+
+		\add_settings_field(
+			'activitypub_authorized_fetch',
+			__( 'Authorized Fetch', 'activitypub' ),
+			array( self::class, 'render_authorized_fetch_field' ),
+			'activitypub_advanced_settings',
+			'activitypub_advanced_settings',
+			array( 'label_for' => 'activitypub_authorized_fetch' )
+		);
+	}
+
+	/**
+	 * Render Advanced Settings Section.
+	 */
+	public static function render_advanced_settings_section() {
+		?>
+		<p>
+			<?php
+			$allowed_html = array(
+				'a' => array(
+					'href'   => true,
+					'target' => true,
+				),
+			);
+			echo wp_kses( __( 'Advanced settings allow deep customization but can affect your site&rsquo;s functionality, security, or performance if misconfigured. Only proceed if you fully understand the changes, and always back up your site beforehand. If unsure, consult <a href="https://github.com/Automattic/wordpress-activitypub/tree/trunk/docs" target="_blank">documentation</a> or seek <a href="https://wordpress.org/support/plugin/activitypub/" target="_blank">expert advice</a>.', 'activitypub' ), $allowed_html );
path: includes/wp-admin/class-advanced-settings-fields.php
user: login: obenland
body: ```suggestion
			echo wp_kses( __( 'Advanced settings allow deep customization but can affect your site&#8217;s functionality, security, or performance if misconfigured. Only proceed if you fully understand the changes, and always back up your site beforehand. If unsure, consult <a href="https://github.com/Automattic/wordpress-activitypub/tree/trunk/docs" target="_blank">documentation</a> or seek <a href="https://wordpress.org/support/plugin/activitypub/" target="_blank">expert advice</a>.', 'activitypub' ), $allowed_html );
```
Just to double-check, we're fine with making the URLs translatable?
I don't know if it's a convention or something others have done for security purposes, but I've mostly seen URLs formatted in to avoid translators accidentally breaking them, unless there was a localized version, like for `https://wordpress.org/support/plugin/activitypub/` for example.
Not sure what's the most right thing here, just want to make sure we make that decision consciously. 
created_at: 2025-04-02T15:20:45Z
author_association: MEMBER
diff_hunk: @@ -498,6 +499,25 @@ public static function theme_compat() {
 		}
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
+		if ( ! \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			return $pre;
+		}
+
+		if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
+			return '1';
+		}
+
+		return '0';
path: includes/class-activitypub.php
user: login: obenland
body: If we go ahead with that change, function docs would need to be updated, too
created_at: 2025-04-02T15:23:24Z
author_association: MEMBER
diff_hunk: @@ -498,6 +499,25 @@ public static function theme_compat() {
 		}
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
+		if ( ! \defined( 'ACTIVITYPUB_AUTHORIZED_FETCH' ) ) {
+			return $pre;
+		}
+
+		if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
+			return '1';
+		}
+
+		return '0';
path: includes/class-activitypub.php
user: login: pfefferle
body: I would keep it as is, to be compatible with how `get_option` and `get_user_meta` works...
created_at: 2025-04-02T15:45:41Z
author_association: MEMBER
diff_hunk: @@ -498,6 +499,25 @@ public static function theme_compat() {
 		}
 	}
 
+	/**
+	 * Pre-get option filter for the Authorized Fetch.
+	 *
+	 * @param string $pre The pre-get option value.
+	 *
+	 * @return string If the constant is defined, return the value, otherwise return the pre-get option value.
+	 */
+	public static function pre_option_activitypub_authorized_fetch( $pre ) {
path: includes/class-activitypub.php
user: login: obenland
body: You want to keep it as is?
created_at: 2025-04-02T16:57:43Z
author_association: MEMBER

PR comments:
user: login: obenland
created_at: 2025-04-01T20:38:37Z
body: Not sure if I'm blocking progress for this PR?
author_association: MEMBER
user: login: pfefferle
created_at: 2025-04-02T07:03:21Z
body: @obenland I am not sure if "blocking" is the right term here. I think we have different views on this and need a way to find a solution on how to finally build it!

For now I simply try to keep the PR up to date, so that the merge conflicts are not getting worse.
author_association: MEMBER
user: login: obenland
created_at: 2025-04-02T12:38:38Z
body: I can disagree with and commit to the separate tab. Let's move from the `activitypub_show_advanced_tab` option to user meta for parity with how we show the welcome screen, though
author_association: MEMBER
user: login: pfefferle
created_at: 2025-04-02T17:06:35Z
body: What do you mean by move?
author_association: MEMBER
user: login: pfefferle
created_at: 2025-04-02T17:07:27Z
body: I think I've done that 😳
author_association: MEMBER
user: login: obenland
created_at: 2025-04-02T17:36:10Z
body: I meant to move `pre_option_activitypub_authorized_fetch()` below `pre_option_activitypub_actor_mode()` to group them and keep with the order the callbacks are registered
author_association: MEMBER
user: login: pfefferle
created_at: 2025-04-02T18:42:50Z
body: I moved it, but to the wrong place 😂

author_association: MEMBER