Repository: Automattic/jetpack
Pull request #42177: MU WPCOM: Prevent site owner from editing user's account-level fields (2nd try)
Author: login: arthur791004
Labels: name: Enhancement
description: Changes to an existing feature — removing, adding, or changing parts of it
name: [Package] Jetpack mu wpcom
description: WordPress.com Features
name: [Plugin] mu wpcom
description: jetpack-mu-wpcom plugin
name: [Plugin] Wpcomsh
name: [mu wpcom Feature] Wpcom Profile Settings
name: I don't care about code coverage for this PR
description: Use this label to ignore the check for insufficient code coveage.
name: [mu wpcom Feature] Wpcom User Edit
Created: 2025-03-03T06:10:56Z | Merged: 2025-03-05T06:55:08Z

<!--- Provide a general summary of your changes in the Title above -->
<!-- Would you like this feature to be tested by Beta testers?
Please add testing instructions to projects/plugins/jetpack/to-test.md in a new commit as part of your PR. -->
<!-- a12s: If you have an expected version that you're aiming for the PR to add, please use the Milestone field to communicate it. If you leave it blank, that indicates there isn't a preference. -->

Fixes https://github.com/Automattic/dotcom-forge/issues/10509

## Proposed changes:
<!--- Explain what functional changes your PR includes -->

**UPDATED**

Ignore super admin as we still have requirements to update the user's email via super admin. See p1740681792682389-slack-C02FMH4G8.

---

* Prevent site owner from editing user's account-level fields if the user is connected to WordPress.com

| Before | After |
| - | - |
| ![Screenshot 2025-02-26 at 3 26 57 PM](https://github.com/user-attachments/assets/617f0008-738c-437a-ac9b-4d50c31ecb6f) | ![Screenshot 2025-02-26 at 3 25 22 PM](https://github.com/user-attachments/assets/630306d0-131f-4fb5-8f41-e6693ff5b760) |

### Other information:

- [ ] Have you written new tests for your changes, if applicable?
- [ ] Have you checked the E2E test CI results, and verified that your changes do not break them?
- [ ] Have you tested your changes on WordPress.com, if applicable (if so, you'll see a generated comment below with a script to run)?

## Jetpack product discussion
<!-- If you're an Automattician, include a shortlink to the p2 discussion with Jetpack Product here. -->
<!-- Make sure any changes to existing products have been discussed and agreed upon -->

## Does this pull request change what data or activity we track or use?
<!--- If so, please add the "[Status] Needs Privacy Updates" label and explain what changes there are. -->
<!--- Check existing Jetpack support documents for a preview of the information we need. -->

## 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. -->

** Site Owner**

* Go to WP Admin as the site owner
* Navigate to All Users
* Edit a user that is connected to WordPress.com except yourself
* Ensure the following fields cannot be changed
  * Language
  * First Name
  * Last Name
  * Nickname
  * Display Name
  * Email
  * Website
  * Biographical Info
* Edit a user that is NOT connected to WordPress.com
* Ensure nothing changed

**Network Admin**

* Apply changes to your sandbox
* Sandbox wordpress.com
* Go to users.php under the Network Admin
* Edit a user
* Ensure the following fields can still be changed
  * Language
  * First Name
  * Last Name
  * Nickname
  * Display Name
  * Email
  * Website
  * Biographical Info


Diff:
diff --git a/projects/packages/jetpack-mu-wpcom/changelog/feat-disable-account-fields-on-edit-user b/projects/packages/jetpack-mu-wpcom/changelog/feat-disable-account-fields-on-edit-user
new file mode 100644
index 000000000000..05b5a44a5a93
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/changelog/feat-disable-account-fields-on-edit-user
@@ -0,0 +1,4 @@
+Significance: minor
+Type: changed
+
+MU WPCOM: Prevent site owner from editing user's account-level fields
diff --git a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
index 42b9b2bc5c48..be8b86fd5679 100644
--- a/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
+++ b/projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php
@@ -171,6 +171,7 @@ public static function load_wpcom_user_features() {
 		require_once __DIR__ . '/features/wpcom-profile-settings/profile-settings-notices.php';
 		require_once __DIR__ . '/features/wpcom-sidebar-notice/wpcom-sidebar-notice.php';
 		require_once __DIR__ . '/features/wpcom-themes/wpcom-themes.php';
+		require_once __DIR__ . '/features/wpcom-user-edit/wpcom-user-edit.php';
 
 		// Only load the Calypsoify and Masterbar features on WoA sites.
 		if ( class_exists( '\Automattic\Jetpack\Status\Host' ) && ( new \Automattic\Jetpack\Status\Host() )->is_woa_site() ) {
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php
index 4e1a277a3449..f4a7838a2f0c 100644
--- a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php
+++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php
@@ -7,18 +7,7 @@
 
 use Automattic\Jetpack\Jetpack_Mu_Wpcom;
 
-/**
- * Check if the site is a WordPress.com Atomic site.
- *
- * @return bool
- */
-function is_woa_site() {
-	if ( ! class_exists( 'Automattic\Jetpack\Status\Host' ) ) {
-		return false;
-	}
-	$host = new Automattic\Jetpack\Status\Host();
-	return $host->is_woa_site();
-}
+require_once __DIR__ . '/../../utils.php';
 
 /**
  * Adds a link to the WordPress.com profile settings page.
diff --git a/projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
new file mode 100644
index 000000000000..b7d8301f57e4
--- /dev/null
+++ b/projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Prevent the site owner from editing user's account-level fields.
+ *
+ * @package automattic/jetpack-mu-wpcom
+ */
+
+namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
+
+require_once __DIR__ . '/../../utils.php';
+
+/**
+ * Disable the account-level fields of the connected users to prevent the site owner from editing them.
+ */
+function wpcom_disable_account_level_fields_if_needed() {
+	// Bail if editing from network.
+	if ( is_network_admin() ) {
+		return;
+	}
+
+	$user_id = ! empty( $_REQUEST['user_id'] ) ? absint( sanitize_text_field( wp_unslash( $_REQUEST['user_id'] ) ) ) : 0; // // phpcs:ignore WordPress.Security.NonceVerification
+
+	// Do nothing if the user is not connected to WordPress.com.
+	if ( ! $user_id || ! is_user_connected( $user_id ) ) {
+		return;
+	}
+
+	?>
+		<script type="text/javascript">
+			document.addEventListener( 'DOMContentLoaded', function() {
+				const fields = [
+					/** Language */
+					{ selector: '#locale' },
+					/** First Name */
+					{ selector: '#first_name' },
+					/** Last Name */
+					{ selector: '#last_name' },
+					/** Nickname */
+					{ selector: '#nickname' },
+					/** Display name */
+					{ selector: '#display_name' },
+					/** Website */
+					{ selector: '#url' },
+					/** Biographical Info */
+					{ selector: '#description', tagName: 'p' },
+					/** Email */
+					{ selector: '#email' },
+				];
+
+				for ( let i = 0; i < fields.length; i++ ) {
+					const field = fields[i];
+					const element = document.querySelector( field.selector );
+					if ( ! element ) {
+						continue;
+					}
+
+					if ( element.tagName === 'INPUT' ) {
+						element.readOnly = true;
+					} else {
+						element.disabled = true;
+					}
+
+					/**
+					 * Append the description to indicate the field cannot be changed.
+					 */
+					const tagName = field.tagName ? field.tagName : 'span';
+					const description = document.createElement( tagName );
+					description.className = 'description';
+					// Use the `Tab` for spacing to align with other fields.
+					description.innerHTML = "\t<?php echo esc_html__( 'It cannot be changed.', 'jetpack-mu-wpcom' ); ?>";
+					element.parentNode.appendChild( description );
+				}
+			} );
+		</script>
+	<?php
+}
+add_action( 'admin_print_footer_scripts-user-edit.php', __NAMESPACE__ . '\wpcom_disable_account_level_fields_if_needed' );
diff --git a/projects/packages/jetpack-mu-wpcom/src/utils.php b/projects/packages/jetpack-mu-wpcom/src/utils.php
index 126d7b2597a9..c2a4ca36003f 100644
--- a/projects/packages/jetpack-mu-wpcom/src/utils.php
+++ b/projects/packages/jetpack-mu-wpcom/src/utils.php
@@ -154,3 +154,30 @@ function get_wpcom_blog_id() {
 
 	return false;
 }
+
+/**
+ * Check if the site is a WordPress.com Atomic site.
+ *
+ * @return bool
+ */
+function is_woa_site() {
+	if ( ! class_exists( 'Automattic\Jetpack\Status\Host' ) ) {
+		return false;
+	}
+	$host = new Automattic\Jetpack\Status\Host();
+	return $host->is_woa_site();
+}
+
+/**
+ * Whether the current user is connected to WordPress.com.
+ *
+ * @param int $user_id the user identifier. Default is the current user.
+ * @return bool Boolean is the user connected?
+ */
+function is_user_connected( $user_id ) {
+	if ( defined( 'IS_WPCOM' ) && IS_WPCOM ) {
+		return true;
+	}
+
+	return ( new Connection_Manager( 'jetpack' ) )->is_user_connected( $user_id );
+}
diff --git a/projects/plugins/mu-wpcom-plugin/changelog/feat-disable-account-fields-on-edit-user b/projects/plugins/mu-wpcom-plugin/changelog/feat-disable-account-fields-on-edit-user
new file mode 100644
index 000000000000..05b5a44a5a93
--- /dev/null
+++ b/projects/plugins/mu-wpcom-plugin/changelog/feat-disable-account-fields-on-edit-user
@@ -0,0 +1,4 @@
+Significance: minor
+Type: changed
+
+MU WPCOM: Prevent site owner from editing user's account-level fields
diff --git a/projects/plugins/wpcomsh/changelog/feat-disable-account-fields-on-edit-user b/projects/plugins/wpcomsh/changelog/feat-disable-account-fields-on-edit-user
new file mode 100644
index 000000000000..05b5a44a5a93
--- /dev/null
+++ b/projects/plugins/wpcomsh/changelog/feat-disable-account-fields-on-edit-user
@@ -0,0 +1,4 @@
+Significance: minor
+Type: changed
+
+MU WPCOM: Prevent site owner from editing user's account-level fields


Reviews:
user: login: fushar
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T13:33:34Z
user: login: arthur791004
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T03:17:37Z
user: login: arthur791004
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T06:19:58Z
user: login: arthur791004
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T06:25:39Z
user: login: okmttdhr
body: Tested on Atomic and Network Admin and worked great for me 👍 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:17:42Z
user: login: fushar
body: It looks good now 👍 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-04T07:49:49Z

Review comments:
diff_hunk: @@ -0,0 +1,76 @@
+<?php
+/**
+ * Prevent the site owner from editing user's account-level fields.
+ *
+ * @package automattic/jetpack-mu-wpcom
+ */
+
+namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
+
+require_once __DIR__ . '/../../utils.php';
+
+/**
+ * Disable the account-level fields of the connected users to prevent the site owner from editing them.
+ */
+function wpcom_disable_account_level_fields_if_needed() {
+	if ( is_super_admin() ) {
path: projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
user: login: fushar
body: Hmm... on Atomic sites, it looks like site owner will have `is_super_admin()` == `true`?

Administrators on my test Atomic site is still able to modify connected users... could you double check?
created_at: 2025-03-03T13:33:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+<?php
+/**
+ * Prevent the site owner from editing user's account-level fields.
+ *
+ * @package automattic/jetpack-mu-wpcom
+ */
+
+namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
+
+require_once __DIR__ . '/../../utils.php';
+
+/**
+ * Disable the account-level fields of the connected users to prevent the site owner from editing them.
+ */
+function wpcom_disable_account_level_fields_if_needed() {
+	if ( is_super_admin() ) {
path: projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
user: login: arthur791004
body: Hmmm...
created_at: 2025-03-04T03:17:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+<?php
+/**
+ * Prevent the site owner from editing user's account-level fields.
+ *
+ * @package automattic/jetpack-mu-wpcom
+ */
+
+namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
+
+require_once __DIR__ . '/../../utils.php';
+
+/**
+ * Disable the account-level fields of the connected users to prevent the site owner from editing them.
+ */
+function wpcom_disable_account_level_fields_if_needed() {
+	if ( is_super_admin() ) {
path: projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
user: login: arthur791004
body: I changed to use `is_network_admin()` as the original message is to edit the user from the network admin 🫣
created_at: 2025-03-04T06:19:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,76 @@
+<?php
+/**
+ * Prevent the site owner from editing user's account-level fields.
+ *
+ * @package automattic/jetpack-mu-wpcom
+ */
+
+namespace Automattic\Jetpack\Jetpack_Mu_Wpcom;
+
+require_once __DIR__ . '/../../utils.php';
+
+/**
+ * Disable the account-level fields of the connected users to prevent the site owner from editing them.
+ */
+function wpcom_disable_account_level_fields_if_needed() {
+	if ( is_super_admin() ) {
path: projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php
user: login: arthur791004
body: Updated the test instructions for the Network Admin!
created_at: 2025-03-04T06:25:38Z
author_association: CONTRIBUTOR

PR comments:
user: login: github-actions[bot]
created_at: 2025-03-03T06:11:55Z
body: **Thank you for your PR!**

When contributing to Jetpack, we have [a few suggestions](https://github.com/Automattic/jetpack/blob/trunk/.github/PULL_REQUEST_TEMPLATE.md) that can help us test and review your patch:<br>
- :white_check_mark: Include a description of your PR changes.<br>
- :white_check_mark: Add a "[Status]" label (In Progress, Needs Review, ...).<br>
- :white_check_mark: Add a "[Type]" label (Bug, Enhancement, Janitorial, Task).<br>
- :white_check_mark: Add testing instructions.<br>
- :white_check_mark: Specify whether this PR includes any changes to data or privacy.<br>
- :white_check_mark: Add changelog entries to affected projects<br>


This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation :robot:

******

**Follow this PR Review Process:**

1. Ensure all required checks appearing at the bottom of this PR are passing.
2. Make sure to test your changes on all platforms that it applies to. *You're responsible for the quality of the code you ship*.
3. You can use [GitHub's Reviewers functionality](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/requesting-a-pull-request-review) to request a review.
4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!
******

**Mu Wpcom plugin:**

- Next scheduled release: _WordPress.com Simple releases happen semi-continuously (PCYsg-Jjm-p2)_


If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

******

**Wpcomsh plugin:**

- Next scheduled release: _Atomic deploys happen twice daily on weekdays (p9o2xV-2EN-p2)_


If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-03T06:12:38Z
body: <!-- wpcom-reminder-comment -->
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

- To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate [the Jetpack Beta plugin](https://jetpack.com/download-jetpack-beta/). Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (WordPress.com Site Helper), and enable the `feat/disable-account-fields-on-edit-user` branch.
- To test on Simple, run the following command on your sandbox:
```
bin/jetpack-downloader test jetpack-mu-wpcom-plugin feat/disable-account-fields-on-edit-user
```

*Interested in more tips and information?*

- In your local development environment, use the `jetpack rsync` command to sync your changes to a WoA dev blog.
- Read more about our development workflow here: PCYsg-eg0-p2
- Figure out **when your changes will be shipped to customers** here: PCYsg-eg5-p2
author_association: CONTRIBUTOR
user: login: jp-launch-control[bot]
created_at: 2025-03-03T06:17:14Z
body: ### Code Coverage Summary

Coverage changed in 3 files.

| File | Coverage | Δ% | Δ Uncovered |
| :--- | :------: | :---: | :---: |
| projects/packages/jetpack-mu-wpcom/src/utils.php | 4/63 (6.35%) | -0.79% | 7 💔 |
| projects/packages/jetpack-mu-wpcom/src/class-jetpack-mu-wpcom.php | 0/224 (0.00%) | 0.00% | 1 ❤️‍🩹 |
| projects/packages/jetpack-mu-wpcom/src/features/wpcom-profile-settings/profile-settings-link-to-wpcom.php | 0/42 (0.00%) | 0.00% | -3 💚 |

1 file is newly checked for coverage.

| File | Coverage |
| :--- | :------: |
| projects/packages/jetpack-mu-wpcom/src/features/wpcom-user-edit/wpcom-user-edit.php | 0/46 (0.00%) 💔 |



[Full summary](https://jetpackcodecoverage.atomicsites.blog/prs/42177/) · [PHP report](https://jetpackcodecoverage.atomicsites.blog/prs/42177/php/)

<sub>Coverage check overridden by https://github.com/Automattic/jetpack/labels/I%20don%27t%20care%20about%20code%20coverage%20for%20this%20PR.</sub>
author_association: NONE