| 1 |
<?php |
| 2 |
/** |
| 3 |
* Desktop Mode — Native User Edit (single user) window. |
| 4 |
* |
| 5 |
* Singleton native window that opens when an admin clicks an |
| 6 |
* OTHER user's row in the Users table (or when the URL remap for |
| 7 |
* `user-edit.php?user_id=N` fires). The Users window's "Profile" |
| 8 |
* tab handles the **current** user's own profile in-place — this |
| 9 |
* window is the dedicated surface for editing OTHER users. |
| 10 |
* |
| 11 |
* Reuses the same JS render functions |
| 12 |
* (`mountProfileFormAt`, `mountProfileAsideAt`, |
| 13 |
* `mountProfileActivityAt`) that power the Profile tab; the only |
| 14 |
* difference is the wrapper template (no Users tab strip; just |
| 15 |
* the sidebar layout) and the way the target user id is resolved |
| 16 |
* (read from the shared store when a row click / URL remap set a |
| 17 |
* target, falling back to the viewer's own id — which is how |
| 18 |
* `profile.php` opens). |
| 19 |
* |
| 20 |
* @package WPDesktopMode |
| 21 |
*/ |
| 22 |
|
| 23 |
defined( 'ABSPATH' ) || exit; |
| 24 |
|
| 25 |
/** |
| 26 |
* Echoes the User Edit window's template body. |
| 27 |
*/ |
| 28 |
function desktop_mode_user_edit_window_render_template() { |
| 29 |
ob_start(); |
| 30 |
?> |
| 31 |
<div class="desktop-mode-user-edit-window" data-desktop-mode-user-edit-window-root> |
| 32 |
<?php /* |
| 33 |
* The `<wpd-user-profile>` component reads its target |
| 34 |
* user id from the `user-id` attribute and mounts the |
| 35 |
* full profile surface (sidebar + form + activity) on |
| 36 |
* its own. The render callback in `index.ts` sets the |
| 37 |
* attribute after reading the shared store; absent that, |
| 38 |
* the attribute is empty and the component idles until |
| 39 |
* set. */ ?> |
| 40 |
<wpd-user-profile data-wpd-user-profile-host></wpd-user-profile> |
| 41 |
</div> |
| 42 |
<?php |
| 43 |
$html = (string) ob_get_clean(); |
| 44 |
|
| 45 |
/** |
| 46 |
* Filter the User Edit window's template HTML. |
| 47 |
* |
| 48 |
* @param string $html Default template HTML. |
| 49 |
*/ |
| 50 |
$filtered = (string) apply_filters( 'desktop_mode_user_edit_window_template_html', $html ); |
| 51 |
|
| 52 |
if ( function_exists( 'desktop_mode_kses_native_window_template' ) ) { |
| 53 |
echo desktop_mode_kses_native_window_template( $filtered ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- helper kses-escapes. |
| 54 |
} else { |
| 55 |
echo wp_kses( $filtered, wp_kses_allowed_html( 'post' ) ); |
| 56 |
} |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Build the admin colour scheme list with full colour tuples for |
| 61 |
* each scheme. Mirrors what WP core feeds the |
| 62 |
* `admin_color_scheme_picker` action: each entry carries the |
| 63 |
* scheme `name` and a `colors` tuple (typically 3-4 hex strings) |
| 64 |
* used to render mini-swatch previews — not just a slug list. |
| 65 |
* |
| 66 |
* @return array<string,array{name:string,colors:array<int,string>,icon_colors?:array<string,string>}> |
| 67 |
*/ |
| 68 |
function desktop_mode_user_edit_window_color_schemes() { |
| 69 |
$out = array(); |
| 70 |
// Always pull in `wp-admin/includes/admin.php` if needed so the |
| 71 |
// admin colour scheme registry is available — the user-edit |
| 72 |
// window may render via REST (where `is_admin()` returns false |
| 73 |
// but `current_user_can( 'edit_user', … )` is the real gate). |
| 74 |
// The include is guarded by `is_readable` so a stripped-down |
| 75 |
// install without admin/ on disk still degrades gracefully. |
| 76 |
if ( ! function_exists( 'register_admin_color_schemes' ) ) { |
| 77 |
$admin_inc = ABSPATH . 'wp-admin/includes/admin.php'; |
| 78 |
if ( is_readable( $admin_inc ) ) { |
| 79 |
require_once $admin_inc; |
| 80 |
} |
| 81 |
} |
| 82 |
if ( function_exists( 'register_admin_color_schemes' ) ) { |
| 83 |
register_admin_color_schemes(); |
| 84 |
} |
| 85 |
global $_wp_admin_css_colors; |
| 86 |
if ( is_array( $_wp_admin_css_colors ) ) { |
| 87 |
foreach ( $_wp_admin_css_colors as $slug => $info ) { |
| 88 |
$out[ (string) $slug ] = array( |
| 89 |
'name' => isset( $info->name ) |
| 90 |
? (string) $info->name |
| 91 |
: (string) $slug, |
| 92 |
// The colors stylesheet URL — required for the live |
| 93 |
// preview swap on self-edit. Matches what core's |
| 94 |
// `wp-admin/js/user-profile.js` reads off the |
| 95 |
// `.css_url` hidden field per scheme tile. |
| 96 |
'url' => isset( $info->url ) |
| 97 |
? esc_url_raw( (string) $info->url ) |
| 98 |
: '', |
| 99 |
'colors' => isset( $info->colors ) |
| 100 |
? array_values( (array) $info->colors ) |
| 101 |
: array(), |
| 102 |
'icon_colors' => isset( $info->icon_colors ) |
| 103 |
? (array) $info->icon_colors |
| 104 |
: array(), |
| 105 |
); |
| 106 |
} |
| 107 |
} |
| 108 |
if ( empty( $out ) ) { |
| 109 |
// Fallback so the picker still shows ONE option even when |
| 110 |
// $_wp_admin_css_colors hasn't populated. |
| 111 |
$out['fresh'] = array( |
| 112 |
'name' => __( 'Default', 'desktop-mode' ), |
| 113 |
'colors' => array( '#1d2327', '#2c3338', '#2271b1', '#72aee6' ), |
| 114 |
); |
| 115 |
} |
| 116 |
return $out; |
| 117 |
} |
| 118 |
|
| 119 |
/** |
| 120 |
* Register the User Edit native window on `init`. |
| 121 |
*/ |
| 122 |
function desktop_mode_user_edit_window_register_window() { |
| 123 |
if ( ! desktop_mode_user_edit_window_user_can_register() ) { |
| 124 |
return; |
| 125 |
} |
| 126 |
$viewer_id = (int) get_current_user_id(); |
| 127 |
|
| 128 |
$window_args = array( |
| 129 |
'title' => __( 'Edit user', 'desktop-mode' ), |
| 130 |
'icon' => 'dashicons-admin-users', |
| 131 |
'template' => 'desktop_mode_user_edit_window_render_template', |
| 132 |
// Reuses the Posts bundle. The render callback for |
| 133 |
// `desktop-mode-user-edit` lives in `index.ts` and dispatches |
| 134 |
// to `user-edit-render`'s mount points — same code the Users |
| 135 |
// window's Profile tab uses. |
| 136 |
'script' => 'desktop-mode-posts-window', |
| 137 |
'style' => 'desktop-mode-posts-window', |
| 138 |
'width' => 1100, |
| 139 |
'height' => 760, |
| 140 |
'min_width' => 720, |
| 141 |
'min_height' => 520, |
| 142 |
'placement' => 'none', |
| 143 |
'config' => array( |
| 144 |
'mode' => 'user-edit', |
| 145 |
'restRoot' => esc_url_raw( rest_url() ), |
| 146 |
'restNonce' => wp_create_nonce( 'wp_rest' ), |
| 147 |
'usersUrl' => esc_url_raw( rest_url( 'wp/v2/users' ) ), |
| 148 |
'currentUserId' => $viewer_id, |
| 149 |
'insightsUrlBase' => esc_url_raw( rest_url( 'desktop-mode/v1/users/' ) ), |
| 150 |
'editPostUrlBase' => esc_url_raw( admin_url( 'post.php' ) ), |
| 151 |
// Capability flag so the JS can hide the role select for |
| 152 |
// viewers without `promote_users`. Server-side `update_item` |
| 153 |
// re-checks regardless; this is UX polish. |
| 154 |
'canPromote' => current_user_can( 'promote_users' ), |
| 155 |
// Roles the viewer is allowed to assign — the role select |
| 156 |
// reads this first and falls back to {@see allRoles} for |
| 157 |
// older configs. Surfacing only assignable roles in the |
| 158 |
// dropdown prevents a "pick a role, hit save, get rejected" |
| 159 |
// loop for viewers with `promote_users` but a narrowed |
| 160 |
// `editable_roles` filter (e.g. site managers who can't |
| 161 |
// promote anyone to administrator). |
| 162 |
'assignableRoles' => function_exists( 'desktop_mode_users_window_role_label_map' ) |
| 163 |
? desktop_mode_users_window_role_label_map( $viewer_id ) |
| 164 |
: array(), |
| 165 |
'allRoles' => function_exists( 'desktop_mode_users_window_all_roles_map' ) |
| 166 |
? desktop_mode_users_window_all_roles_map() |
| 167 |
: array(), |
| 168 |
'locales' => function_exists( 'desktop_mode_users_window_locales_map' ) |
| 169 |
? desktop_mode_users_window_locales_map() |
| 170 |
: array( '' => __( 'Site default', 'desktop-mode' ) ), |
| 171 |
'contactMethods' => (array) apply_filters( 'user_contactmethods', array(), null ), |
| 172 |
'colorSchemes' => desktop_mode_user_edit_window_color_schemes(), |
| 173 |
), |
| 174 |
); |
| 175 |
|
| 176 |
$window_args = (array) apply_filters( 'desktop_mode_user_edit_window_args', $window_args ); |
| 177 |
|
| 178 |
$registered = desktop_mode_register_window( 'desktop-mode-user-edit', $window_args ); |
| 179 |
if ( is_wp_error( $registered ) ) { |
| 180 |
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log |
| 181 |
error_log( '[desktop-mode] User Edit window registration failed: ' . $registered->get_error_message() ); |
| 182 |
} |
| 183 |
} |
| 184 |
add_action( 'init', 'desktop_mode_user_edit_window_register_window', 25 ); |
| 185 |
|