Repository: Automattic/wordpress-activitypub
Pull request #1430: Lock settings based on constants
Author: login: pfefferle
Created: 2025-03-07T10:52:39Z | Merged: 2025-03-19T08:18:02Z

This locks the Actor settings based on the `ACTIVITYPUB_SINGLE_USER_MODE`, `ACTIVITYPUB_BLOG_MODE` or `ACTIVITYPUB_ACTOR_MODE` constants.

Before:

<img width="871" alt="Screenshot 2025-03-07 at 11 50 55" src="https://github.com/user-attachments/assets/2776589a-606b-4a66-ba2a-02cce15e4304" />

After:

<img width="889" alt="Screenshot 2025-03-07 at 11 50 40" src="https://github.com/user-attachments/assets/58aaaf8a-dc3b-4a45-b5bd-6bea377c618f" />

This is the first step to enable the settings on WordPress.com without confusing the user!

## Proposed changes:
<!--- Explain what functional changes your PR includes -->
* It locks the settings to the Actor-Mode that is defined by a constant, if it is.

### 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 '..'
*




Diff:
diff --git a/.github/changelog/change-setting-constants b/.github/changelog/change-setting-constants
new file mode 100644
index 000000000..035c73083
--- /dev/null
+++ b/.github/changelog/change-setting-constants
@@ -0,0 +1,4 @@
+Significance: minor
+Type: changed
+
+Restricted modifications to settings if they are predefined as constants.
diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php
index 414922716..ac0d3eadc 100644
--- a/includes/class-activitypub.php
+++ b/includes/class-activitypub.php
@@ -49,6 +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_action( 'init', array( self::class, 'register_user_meta' ), 11 );
 
 		// Register several post_types.
@@ -383,6 +385,29 @@ public static function pre_get_avatar_data( $args, $id_or_email ) {
 		return $args;
 	}
 
+	/**
+	 * Pre-get option filter for the Actor-Mode.
+	 *
+	 * @param string|false $pre The pre-get option value.
+	 *
+	 * @return string|false The actor mode or false if it should not be filtered.
+	 */
+	public static function pre_get_option( $pre ) {
+		if ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) {
+			return ACTIVITYPUB_BLOG_MODE;
+		}
+
+		if ( \defined( 'ACTIVITYPUB_DISABLE_USER' ) && ACTIVITYPUB_DISABLE_USER ) {
+			return ACTIVITYPUB_BLOG_MODE;
+		}
+
+		if ( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) && ACTIVITYPUB_DISABLE_BLOG_USER ) {
+			return ACTIVITYPUB_ACTOR_MODE;
+		}
+
+		return $pre;
+	}
+
 	/**
 	 * Function to retrieve Avatar URL if stored in meta.
 	 *
diff --git a/includes/wp-admin/class-settings-fields.php b/includes/wp-admin/class-settings-fields.php
index 85359d4ee..6f162a90e 100644
--- a/includes/wp-admin/class-settings-fields.php
+++ b/includes/wp-admin/class-settings-fields.php
@@ -223,51 +223,63 @@ public static function render_notifications_section() {
 	 * Render actor mode field.
 	 */
 	public static function render_actor_mode_field() {
+		$disabled = ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_USER' ) && ACTIVITYPUB_DISABLE_USER ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) && ACTIVITYPUB_DISABLE_BLOG_USER );
+
+		if ( $disabled ) :
+			?>
+			<p class="description">
+				<?php esc_html_e( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator.', 'activitypub' ); ?>
+			</p>
+			<?php
+			return;
+		endif;
+
 		$value = get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
 		?>
-			<fieldset class="actor-mode-selection">
-				<div class="row">
-					<input type="radio" id="actor-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_ACTOR_MODE ); ?>" <?php checked( ACTIVITYPUB_ACTOR_MODE, $value ); ?> />
-					<div>
-						<label for="actor-mode"><strong><?php esc_html_e( 'Author Profiles Only', 'activitypub' ); ?></strong></label>
-						<p class="description">
-							<?php echo wp_kses( __( 'Every author on this blog (with the <code>activitypub</code> capability) gets their own ActivityPub profile.', 'activitypub' ), array( 'code' => array() ) ); ?>
-							<strong>
-								<?php
-								echo wp_kses(
-									sprintf(
-									// translators: %s is a URL.
-										__( 'You can add/remove the capability in the <a href="%s">user settings.</a>', 'activitypub' ),
-										admin_url( '/users.php' )
-									),
-									array( 'a' => array( 'href' => array() ) )
-								);
-								?>
-							</strong>
-							<?php echo wp_kses( __( 'Select all the users you want to update, choose the method from the drop-down list and click on the "Apply" button.', 'activitypub' ), array( 'code' => array() ) ); ?>
-						</p>
-					</div>
-				</div>
-				<div class="row">
-					<input type="radio" id="blog-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_BLOG_MODE ); ?>" <?php checked( ACTIVITYPUB_BLOG_MODE, $value ); ?> />
-					<div>
-						<label for="blog-mode"><strong><?php esc_html_e( 'Blog profile only', 'activitypub' ); ?></strong></label>
-						<p class="description">
-							<?php esc_html_e( 'Your blog becomes a single ActivityPub profile and every post will be published under this profile instead of the individual author profiles.', 'activitypub' ); ?>
-						</p>
-					</div>
+		<fieldset class="actor-mode-selection">
+			<div class="row">
+				<input type="radio" id="actor-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_ACTOR_MODE ); ?>" <?php checked( ACTIVITYPUB_ACTOR_MODE, $value ); ?> />
+				<div>
+					<label for="actor-mode"><strong><?php esc_html_e( 'Author Profiles Only', 'activitypub' ); ?></strong></label>
+					<p class="description">
+						<?php echo wp_kses( __( 'Every author on this blog (with the <code>activitypub</code> capability) gets their own ActivityPub profile.', 'activitypub' ), array( 'code' => array() ) ); ?>
+						<strong>
+							<?php
+							echo wp_kses(
+								sprintf(
+								// translators: %s is a URL.
+									__( 'You can add/remove the capability in the <a href="%s">user settings.</a>', 'activitypub' ),
+									admin_url( '/users.php' )
+								),
+								array( 'a' => array( 'href' => array() ) )
+							);
+							?>
+						</strong>
+						<?php echo wp_kses( __( 'Select all the users you want to update, choose the method from the drop-down list and click on the "Apply" button.', 'activitypub' ), array( 'code' => array() ) ); ?>
+					</p>
 				</div>
-				<div class="row">
-					<input type="radio" id="actor-blog-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_ACTOR_AND_BLOG_MODE ); ?>" <?php checked( ACTIVITYPUB_ACTOR_AND_BLOG_MODE, $value ); ?> />
-					<div>
-						<label for="actor-blog-mode"><strong><?php esc_html_e( 'Both author and blog profiles', 'activitypub' ); ?></strong></label>
-						<p class="description">
-							<?php esc_html_e( "This combines both modes. Users can be followed individually, while following the blog will show boosts of individual user's posts.", 'activitypub' ); ?>
-						</p>
-					</div>
+			</div>
+			<div class="row">
+				<input type="radio" id="blog-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_BLOG_MODE ); ?>" <?php checked( ACTIVITYPUB_BLOG_MODE, $value ); ?> />
+				<div>
+					<label for="blog-mode"><strong><?php esc_html_e( 'Blog profile only', 'activitypub' ); ?></strong></label>
+					<p class="description">
+						<?php esc_html_e( 'Your blog becomes a single ActivityPub profile and every post will be published under this profile instead of the individual author profiles.', 'activitypub' ); ?>
+					</p>
 				</div>
-			</fieldset>
-
+			</div>
+			<div class="row">
+				<input type="radio" id="actor-blog-mode" name="activitypub_actor_mode" value="<?php echo esc_attr( ACTIVITYPUB_ACTOR_AND_BLOG_MODE ); ?>" <?php checked( ACTIVITYPUB_ACTOR_AND_BLOG_MODE, $value ); ?> />
+				<div>
+					<label for="actor-blog-mode"><strong><?php esc_html_e( 'Both author and blog profiles', 'activitypub' ); ?></strong></label>
+					<p class="description">
+						<?php esc_html_e( "This combines both modes. Users can be followed individually, while following the blog will show boosts of individual user's posts.", 'activitypub' ); ?>
+					</p>
+			</div>
+		</div>
+	</fieldset>
 		<?php
 	}
 


Reviews:
user: login: obenland
body: Thanks, this make me feel much better! I think this is good to go as is, any suggestions are purely optional.
state: DISMISSED
author_association: MEMBER
submitted_at: 2025-03-18T16:40:36Z
user: login: obenland
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-19T07:55:31Z

Review comments:
diff_hunk: @@ -223,8 +223,19 @@ public static function render_notifications_section() {
 	 * Render actor mode field.
 	 */
 	public static function render_actor_mode_field() {
-		$value = get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
-		?>
+		$disabled = ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_USER' ) && ACTIVITYPUB_DISABLE_USER ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) && ACTIVITYPUB_DISABLE_BLOG_USER );
+
+		if ( $disabled ) :
+			?>
+			<p class="description">
+				<?php esc_html_e( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator and cannot be changed. Please contact them if you need different settings.', 'activitypub' ); ?>
path: includes/wp-admin/class-settings-fields.php
user: login: obenland
body: Optional: Limiting the message to "⚠ This setting is defined through server configuration by your blog&#8217;s administrator." could help with readers accepting the status quo and not feeling the need to change it. As in, Admin already took care of it.
Whereas communicating it cannot be changed and to get in touch with the admin to change it, might suggest something is wrong and needs changing.
created_at: 2025-03-18T16:39:32Z
author_association: MEMBER
diff_hunk: @@ -223,8 +223,19 @@ public static function render_notifications_section() {
 	 * Render actor mode field.
 	 */
 	public static function render_actor_mode_field() {
-		$value = get_option( 'activitypub_actor_mode', ACTIVITYPUB_ACTOR_MODE );
-		?>
+		$disabled = ( \defined( 'ACTIVITYPUB_SINGLE_USER_MODE' ) && ACTIVITYPUB_SINGLE_USER_MODE ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_USER' ) && ACTIVITYPUB_DISABLE_USER ) ||
+						( \defined( 'ACTIVITYPUB_DISABLE_BLOG_USER' ) && ACTIVITYPUB_DISABLE_BLOG_USER );
+
+		if ( $disabled ) :
+			?>
+			<p class="description">
+				<?php esc_html_e( '⚠ This setting is defined through server configuration by your blog&#8217;s administrator and cannot be changed. Please contact them if you need different settings.', 'activitypub' ); ?>
+			</p>
+			<?php
+		else :
path: includes/wp-admin/class-settings-fields.php
user: login: obenland
body: Optional: You could `return;` here as well.
created_at: 2025-03-18T16:39:53Z
author_association: MEMBER

PR comments:
user: login: obenland
created_at: 2025-03-10T13:10:14Z
body: Could we remove that settings field instead?
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-10T13:29:15Z
body: We could :)

I like the locked screen, so that it is visible and might help with debugging!?!
author_association: MEMBER
user: login: obenland
created_at: 2025-03-10T13:54:04Z
body: I'd love to learn more about the debugging aspect, could you expand on that?

Rather than solving it with constants, I'd suggest we register a Dotcom-specific settings field that displays these as disabled instead? I think it would be help with keeping things simple for their intended use-cases.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-10T14:02:27Z
body: This is not directly for dotcom! This is a general fix, that should show that this setting is already done through a constant. For dotcom I would vote for unhook the setting on top of this change!
author_association: MEMBER
user: login: obenland
created_at: 2025-03-10T14:58:48Z
body: Ah, the issue description mentions Dotcom specifically.

In that case, I'd still come back to not showing the setting at all, rather than possibly confusing folks with something they can't edit.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-10T16:05:13Z
body: Ah, this was more, the part that hooks into the options! So that even the option calls care for the constants.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-10T16:09:34Z
body: > In that case, I'd still come back to not showing the setting at all, rather than possibly confusing folks with something they can't edit.

Funny, that is exactly the case why I kept them. I fear that it confuses users if they can't find settings and features without us explaining it. Aside from that it makes it hard for us to find out what `const` is used to overrule the user settings. 
author_association: MEMBER
user: login: obenland
created_at: 2025-03-10T16:41:37Z
body: > I fear that it confuses users if they can't find settings and features without us explaining it

Oh, you mean in the transition from one plugin version to the next. Yeah. We could show a banner with an explanatory text instead of the setting, and then remove it in the next version?
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-10T18:09:20Z
body: No, I mean in general! A missing setting is always problematic, because people assume that it is there. The other point is, that it is harder for us to get to know the constant, because the user can't tell us if the block is hidden. This is not meant for WordPress.com users! This is if a various hoster or admin decides to configure the setting using a const.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-11T10:32:45Z
body: Removing the settings completely looks weird, because it is currently the only setting in `Profiles`

<img width="887" alt="Screenshot 2025-03-11 at 11 30 36" src="https://github.com/user-attachments/assets/3131a135-69bc-4633-9705-90ff84f46bd6" />

author_association: MEMBER
user: login: obenland
created_at: 2025-03-11T16:07:49Z
body: Yeah, we'd have to remove the corresponding section as well.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-11T16:13:54Z
body: > Yeah, we'd have to remove the corresponding section as well.

But then you might remove all other custom settings that are hooked into the Profiles section. It starts to get complicated 🫣
author_association: MEMBER
user: login: obenland
created_at: 2025-03-11T18:39:34Z
body: Oh, right, it's not just for Dotcom. Yeah, maybe we should display an info notice instead of the field?
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-11T18:42:18Z
body: @obenland I would recommend to keep that as is, because it already brings a lot of improvements and makes settings consistent, and work on a follow up that maybe hides the settings instead of disabling them. I can't think of a way to hide a section the correct way, without a hack or weird workaround.

So maybe we rethink that feature in the context of WordPress.com Simple Sites.
author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-17T15:11:53Z
body: @obenland so what do you think about my last comment?
author_association: MEMBER
user: login: obenland
created_at: 2025-03-18T15:16:35Z
body: Maybe only show the notice and hide the settings themselves?

<img width="1582" alt="image" src="https://github.com/user-attachments/assets/e84eb6f4-57c3-4661-a0b1-0dc03639f5f6" />

author_association: MEMBER
user: login: pfefferle
created_at: 2025-03-18T16:32:43Z
body: @obenland done!
author_association: MEMBER