PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 1.1.2
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v1.1.2
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / includes / agents / identity.php

identity.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 1.1.2, at includes/agents/identity.php

243 lines 7.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * OpenStation — Agents: identity layer (synthetic WordPress users).
4 *
5 * Each agent has a real row in `wp_users` so capability checks, edit
6 * locks, comment attribution, and the standard WP audit trail work
7 * without a parallel ACL. The row is "synthetic" only in that every
8 * login and session path is blocked — the agent never authenticates;
9 * it is invoked on the site's behalf.
10 *
11 * Those blocks, and `openstation_agent_is_agent()` itself, live in
12 * guard.php, which loads unconditionally. This file owns the row
13 * lifecycle (create / delete) and the identity surface: the bot avatar
14 * and the wp-admin Users list "Type" column, so administrators can
15 * tell synthetic accounts apart.
16 *
17 * Definition meta constants live in store.php.
18 *
19 * @package OpenStation
20 */
21
22 defined( 'ABSPATH' ) || exit;
23
24 require_once OPENSTATION_DIR . 'includes/agents/guard.php';
25
26 /**
27 * Resolve a unique `user_login` for an agent given its desired slug.
28 *
29 * Returns the input prefixed with `agent-`, or appends a numeric suffix
30 * if a user with that login already exists.
31 *
32 * @param string $slug Sanitized slug.
33 * @return string
34 */
35 function openstation_agent_resolve_unique_login( $slug ) {
36 $base = 'agent-' . $slug;
37 $login = $base;
38 $counter = 1;
39 while ( username_exists( $login ) ) {
40 ++$counter;
41 $login = $base . '-' . $counter;
42 }
43 return $login;
44 }
45
46 /**
47 * Build a synthetic, RFC-shaped email for an agent.
48 *
49 * The address is never sent to — it just satisfies `wp_insert_user`'s
50 * schema validation and reserves the slot so `email_exists()` stays
51 * unique across agents.
52 *
53 * @param string $slug Sanitized agent slug.
54 * @return string
55 */
56 function openstation_agent_synthetic_email( $slug ) {
57 $host = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
58 if ( ! is_string( $host ) || '' === $host ) {
59 $host = 'invalid.local';
60 }
61 $email = $slug . '@agents.' . $host;
62 $counter = 1;
63 while ( email_exists( $email ) ) {
64 ++$counter;
65 $email = $slug . '+' . $counter . '@agents.' . $host;
66 }
67 return $email;
68 }
69
70 /**
71 * Create a synthetic agent user row. Definition meta is written by the
72 * `openstation_agent_create()` orchestrator in store.php — call that,
73 * not this, unless you only need the bare row.
74 *
75 * @param array{name:string, role:string, slug?:string} $args Agent
76 * creation args. `role` MUST be one of the site's registered
77 * roles. `slug` defaults to `sanitize_title( $name )`.
78 * @return WP_User|WP_Error
79 */
80 function openstation_agent_create_user( $args ) {
81 $name = isset( $args['name'] ) ? trim( (string) $args['name'] ) : '';
82 $role = isset( $args['role'] ) ? sanitize_key( $args['role'] ) : '';
83 $slug = isset( $args['slug'] ) ? sanitize_title( $args['slug'] ) : '';
84
85 if ( '' === $name ) {
86 return new WP_Error(
87 'openstation_agent_invalid_name',
88 __( 'Agent name is required.', 'desktop-mode' )
89 );
90 }
91
92 $roles = wp_roles()->get_names();
93 if ( '' === $role || ! isset( $roles[ $role ] ) ) {
94 return new WP_Error(
95 'openstation_agent_invalid_role',
96 __( 'Pick a valid WordPress role for the agent.', 'desktop-mode' )
97 );
98 }
99
100 if ( '' === $slug ) {
101 $slug = sanitize_title( $name );
102 }
103 if ( '' === $slug ) {
104 return new WP_Error(
105 'openstation_agent_invalid_slug',
106 __( 'Agent slug could not be derived from the name.', 'desktop-mode' )
107 );
108 }
109
110 $user_id = wp_insert_user(
111 array(
112 'user_login' => openstation_agent_resolve_unique_login( $slug ),
113 'user_email' => openstation_agent_synthetic_email( $slug ),
114 'user_pass' => wp_generate_password( 64, true, true ),
115 'display_name' => $name,
116 'nickname' => $name,
117 'role' => $role,
118 'show_admin_bar_front' => false,
119 )
120 );
121
122 if ( is_wp_error( $user_id ) ) {
123 return $user_id;
124 }
125
126 update_user_meta( $user_id, OPENSTATION_AGENT_USER_MARKER_META, '1' );
127
128 return new WP_User( $user_id );
129 }
130
131 /**
132 * Delete an agent user. Definition meta rows die with the user
133 * (`wp_delete_user()` removes all usermeta). Content the agent
134 * authored is NOT reassigned — pass a reassign id when the caller
135 * wants to keep it.
136 *
137 * @param int $user_id Agent user id.
138 * @param int|null $reassign Optional user id to reassign authored content to.
139 * @return true|WP_Error
140 */
141 function openstation_agent_delete( $user_id, $reassign = null ) {
142 if ( ! openstation_agent_is_agent( $user_id ) ) {
143 return new WP_Error(
144 'openstation_agent_not_an_agent',
145 __( 'User is not a OpenStation agent.', 'desktop-mode' )
146 );
147 }
148
149 if ( ! function_exists( 'wp_delete_user' ) ) {
150 require_once ABSPATH . 'wp-admin/includes/user.php';
151 }
152
153 $deleted = wp_delete_user( (int) $user_id, $reassign );
154 if ( ! $deleted ) {
155 return new WP_Error(
156 'openstation_agent_delete_failed',
157 __( 'Could not delete the agent user.', 'desktop-mode' )
158 );
159 }
160
161 /**
162 * Fires after an agent is deleted.
163 *
164 * @param int $user_id Agent user id (row no longer exists when this fires).
165 * @param int $actor_id User who deleted the agent.
166 */
167 do_action( 'openstation_agent_deleted', (int) $user_id, get_current_user_id() );
168
169 return true;
170 }
171
172 // ---------------------------------------------------------------------------
173 // Identity surface
174 //
175 // `openstation_agent_avatar_url()` lives in bootstrap.php: the WP
176 // Explorer integration needs it for the entity icon and loads while the
177 // feature flag is off, when this file does not.
178 // ---------------------------------------------------------------------------
179
180 /**
181 * Substitute the bot glyph for agent avatars across the WP admin.
182 *
183 * @param array $args Args being assembled by `get_avatar_data()`.
184 * @param int|string|WP_User|WP_Comment $id_or_email Identifier the caller passed.
185 * @return array
186 */
187 function openstation_agent_avatar( $args, $id_or_email ) {
188 $user_id = 0;
189 if ( is_numeric( $id_or_email ) ) {
190 $user_id = (int) $id_or_email;
191 } elseif ( $id_or_email instanceof WP_User ) {
192 $user_id = (int) $id_or_email->ID;
193 } elseif ( $id_or_email instanceof WP_Comment ) {
194 $user_id = (int) $id_or_email->user_id;
195 } elseif ( is_string( $id_or_email ) && is_email( $id_or_email ) ) {
196 $user = get_user_by( 'email', $id_or_email );
197 if ( $user ) {
198 $user_id = (int) $user->ID;
199 }
200 }
201
202 if ( $user_id > 0 && openstation_agent_is_agent( $user_id ) ) {
203 $args['url'] = openstation_agent_avatar_url();
204 $args['found_avatar'] = true;
205 }
206 return $args;
207 }
208 add_filter( 'pre_get_avatar_data', 'openstation_agent_avatar', 10, 2 );
209
210 /**
211 * Add a "Type" column to the wp-admin Users list that labels agents.
212 *
213 * @param string[] $columns Existing column id => label map.
214 * @return string[]
215 */
216 function openstation_agent_users_columns( $columns ) {
217 $columns['openstation_agent_type'] = __( 'Type', 'desktop-mode' );
218 return $columns;
219 }
220 add_filter( 'manage_users_columns', 'openstation_agent_users_columns' );
221
222 /**
223 * Render the cell for the "Type" column.
224 *
225 * @param string $output Existing rendered HTML.
226 * @param string $column_name Column id.
227 * @param int $user_id User id being rendered.
228 * @return string
229 */
230 function openstation_agent_users_custom_column( $output, $column_name, $user_id ) {
231 if ( 'openstation_agent_type' !== $column_name ) {
232 return $output;
233 }
234 if ( openstation_agent_is_agent( $user_id ) ) {
235 return '<span class="os-agent-type" aria-label="' . esc_attr__( 'OpenStation agent', 'desktop-mode' ) . '">'
236 . '<span class="dashicons dashicons-superhero" aria-hidden="true"></span> '
237 . esc_html__( 'Agent', 'desktop-mode' )
238 . '</span>';
239 }
240 return '<span class="os-agent-type-human">' . esc_html__( 'Person', 'desktop-mode' ) . '</span>';
241 }
242 add_filter( 'manage_users_custom_column', 'openstation_agent_users_custom_column', 10, 3 );
243