Repository navigation
Users: Show the profile sections other plugins add in the native profile - #1021
Conversation
The User Edit window and the Users window's Profile tab edit the core fields over REST, so nothing a plugin hooks onto the profile screens appeared there (show_user_profile, edit_user_profile, personal_options, profile_personal_options), and personal_options_update / edit_user_profile_update never ran. When the screen has callbacks on any of those hooks, <os-user-profile> now embeds the real profile.php / user-edit.php below its form with wp.os.embedAdminPage(), narrowed with CSS to what the hooks print. The plugin's markup, scripts, nonces and save hook run as on the classic page. The element routes the embed's links, follows a wp-login.php re-authentication back to the narrowed screen, hides the section when the hooks print nothing, and reloads the embed after its own form saves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
79b11e5 to
db258cd
Compare
|
Thanks so much @juanlentino, embedding the real Two things I could reproduce. When the embedded page isn't a profile form (for example a user that doesn't exist, which returns "Invalid user ID."), |
The embed re-narrowed any page on the profile screens that lacked the
marker, including pages that are not the profile form ("Invalid user
ID."), so it reloaded without end. It now re-narrows only the profile
form, and once per navigation.
A save from the native form reloaded the embed and dropped edits made in
it. Now the embed is reloaded only when nothing in it was edited.
Otherwise the fields left alone take the server's fresh values and the
embed saves itself, so neither the plugin edit nor the core change is
lost.
Trunk's embeds route admin links themselves since WordPress#952, so the element
no longer does (it would open a second window) and the openUrl property
is gone. Links to the profile screens and wp-login.php stay in the embed
through a capture-phase listener on the narrowed page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for testing it, @epeicher. Both are fixed in the latest push.
I also merged trunk. Since #952 embeds route admin links themselves, so the element no longer does, which would have opened a second window. Links to the profile screen and to |
epeicher
left a comment
There was a problem hiding this comment.
Thanks, Juan, for your great contributions! I have reviewed this and LGTM!
Fixes #1020
What this changes
The native User Edit window and the Users window's Profile tab (
<os-user-profile>) now show what other plugins add to the profile screen, under a "More settings" heading below the native form. Saving it works the same as on the classic screen. This applies to any plugin that uses core's profile hooks:show_user_profile(your own profile) andedit_user_profile(someone else's): the sections after Application Passwords;personal_options: rows inside core's Personal Options table;profile_personal_options: the block right after that table, on your own profile.How
When the screen being shown has callbacks on any of those hooks,
<os-user-profile>embeds the realprofile.phporuser-edit.php?user_id=Nwithwp.os.embedAdminPage(), carryingopenstation_profile_sections=1. On that flag the server marks where each hook's output begins and narrows the page with CSS to that output. This follows the AGENTS.md rule that an admin screen inside a native window goes throughembedAdminPage(), as Posts' Add Post tab does.Nothing is removed from the page. Core's nonce, hidden fields and "Update Profile" button stay in the form, so:
profile.php/user-edit.phpload as usual;personal_options_update/edit_user_profile_updaterun through core's handler;user_profile_update_errorsmessages show as on the classic page.The flag is posted back as a hidden field (the form's action has no query string), and a
wp_redirectfilter keeps it on the redirect after saving.Things an embed does not do on its own, handled in
apps/users/profile/plugin-sections.ts:#profile-pagewrapper and follows it with aResizeObserver. It is not sized from the document height, because the chromeless document is at least as tall as its frame and only grows.wp-login.php(a plugin asking the user to re-authenticate before changing a setting), navigates in place: a small capture-phase listener on the narrowed page lets the browser follow it before the chromeless bridge hands it to the shell. Other admin links are routed the way trunk now routes any embed's admin links (fix(native-windows): switch to list tab on embedded editor back navigation #952). Off-site links (os-external-link) open in a new tab.wp-login.phpre-authentication), it is sent back to the narrowed screen with the same anchor, once, and only when the page is the profile form. A page that is not (for example "Invalid user ID.") stays as it is. The embed URL carriesopenstation_chromeless=1itself, so this does not depend on theSec-Fetchfallback.After the native form saves, the embed reloads when the user has not edited anything in it. Otherwise it would post the core field values it loaded with and could undo the change just made. If the user has edited a field in it, it is not reloaded (that would drop the edit): the fields they left alone take the server's fresh values, fetched again, and the embed saves itself. Neither the plugin edit nor the core change is lost (
apps/users/profile/plugin-sections-form.ts).Files:
apps/users/parts/plugin-sections.php(new): the facts flag, request detection, markers, CSS, redirect filter.apps/users/profile/plugin-sections.ts(new): the embed, sizing, off-site links, empty check, what happens after the native save.apps/users/profile/plugin-sections-form.ts(new): the re-narrow guard, and the form snapshot and refresh for unsaved plugin edits.apps/users/profile/index.ts,types.ts: mount and remount the section, call it after the native save.apps/users/parts/facts.php,apps/users/users.os.php,apps/user-edit/user-edit.os.php,apps/users/users.os.ts,apps/user-edit/user-edit.os.ts: wiring.docs/javascript-reference.md: the<os-user-profile>section.Cost
When any plugin hooks the profile (common on real sites), opening the User Edit window or the Users Profile tab also loads the classic profile screen with its plugins' scripts. A follow-up could defer the embed until the section scrolls into view.
Alternatives considered
ob_start()and render it in the native form. The plugins' scripts would not load, and their save hooks only run on core's form post. There is no precedent for this in the codebase.Limitations
Testing
Local site: WordPress 7.1.3, PHP 8.3, a security plugin that adds a profile section with its own scripts and nonces and requires re-authentication through
wp-login.phpbefore its options can change, and an mu-plugin that adds a field onshow_user_profile/edit_user_profile, a row onpersonal_optionsand a block onprofile_personal_options, saved onpersonal_options_update/edit_user_profile_update. It ran in WordPress Playground CLI because Docker was not available for wp-env.Manual, with the native Users window on:
profile_personal_optionsblock, the mu-plugin section and the security plugin's section show; core sections are hidden.user-edit.php?user_id=2): sections show (edit_user_profile). Saving the field persists. Email and role are unchanged.personal_optionsrow, then Update Profile: "Profile updated", values persist, and the embed stays narrowed after the redirect.wp-login.phpform opens in the embed. After completing it, the plugin redirects toprofile.php#<anchor>, which comes back narrowed and editable.wp-login.phpform inside the embed (no new window), and completing it returns to the narrowedprofile.php#<anchor>.show_user_profilecallback that prints nothing: no "More settings" section.Automated:
npm run buildnpm run typechecknpm run lint: 0 errors (existing warnings only, none in changed files)npm run test:js: 544 files, 6932 tests pass. Four new cases inapps/users/profile/index.test.ts; the re-narrow guard and the refresh cases each fail when their logic is broken.vendor/bin/phpcs -nover the repo (306 files): clean.npm run lint:phpruns inside wp-env, which I could not start, so I ran the same ruleset directly.npm run test:php: two new cases added totests/phpunit/tests/userEditApp.php, not run locally (no Docker). They cover the facts flag for each hook, the marker, and the redirect flag.npm run lint:unused: same output as trunk.Engineered with Claude as a pair programmer.
🤖 Generated with Claude Code