SupportRole.php
8.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
<?php
/**
* Class SupportRole
*
* @package ContentControl\Vendor\TrustedLogin\SupportRole
*
* @copyright 2021 Katz Web Services, Inc.
*
* @license GPL-2.0-or-later
* Modified by code-atlantic on 21-June-2024 using {@see https://github.com/BrianHenryIE/strauss}.
*/
namespace ContentControl\Vendor\TrustedLogin;
// Exit if accessed directly
if ( ! defined('ABSPATH') ) {
exit;
}
use WP_Error;
final class SupportRole {
/**
* @const The capability that is added to the Support Role to indicate that it was created by TrustedLogin.
* @since 1.6.0
*/
const CAPABILITY_FLAG = 'trustedlogin_{ns}_support_role';
/**
* @var Config $config
*/
private $config;
/**
* @var Logging $logging
*/
private $logging;
/**
* @var string $role_name The namespaced name of the new Role to be created for Support Agents
* @example '{vendor/namespace}-support'
*/
private $role_name;
/**
* @var array These capabilities will never be allowed for users created by TrustedLogin.
* @since 1.0.0
*/
static $prevented_caps = array(
'create_users',
'delete_users',
'edit_users',
'list_users',
'promote_users',
'delete_site',
'remove_users',
);
/**
* @var array These roles cannot be deleted by TrustedLogin.
* @since 1.6.0
*/
static $protected_roles = array(
'administrator',
'editor',
'author',
'contributor',
'subscriber',
'wpseo_editor',
'wpseo_manager',
'shop_manager',
'shop_accountant',
'shop_worker',
'shop_vendor',
'customer'
);
/**
* SupportUser constructor.
*/
public function __construct( Config $config, Logging $logging ) {
$this->config = $config;
$this->logging = $logging;
$this->role_name = $this->set_name();
}
/**
* Get the name (slug) of the role that should be cloned for the TL support role
*
* @return string
*/
public function get_cloned_name() {
$roles = $this->config->get_setting( 'role', 'editor' );
// TODO: Support multiple roles
$role = is_array( $roles ) ? array_key_first( $roles ) : $roles;
return (string) $role;
}
/**
* @return string
*/
public function get_name() {
if ( $this->config->get_setting( 'clone_role' ) ) {
return (string) $this->role_name;
}
return (string) $this->config->get_setting( 'role' );
}
/**
* @return string Sanitized with {@uses sanitize_title_with_dashes}
*/
private function set_name( ) {
// If we're not cloning a role, return the existing role name.
if ( ! $this->config->get_setting( 'clone_role' ) ) {
$role_name = (string) $this->config->get_setting( 'role' );
return sanitize_title_with_dashes( $role_name );
}
$default = $this->config->ns() . '-support';
$role_name = apply_filters(
'trustedlogin/' . $this->config->ns() . '/support_role',
$default,
$this
);
if ( ! is_string( $role_name ) ) {
$role_name = $default;
}
return sanitize_title_with_dashes( $role_name );
}
/**
* Returns the Support Role, creating it if it doesn't already exist.
*
* @since 1.6.0
*
* @return \WP_Role|\WP_Error Role, if successful. WP_Error if failure.
*/
public function get() {
// If cloning a role, create and return it.
if ( $this->config->get_setting( 'clone_role' ) ) {
return $this->create();
}
// Otherwise, confirm and return the existing role.
$role_slug = $this->config->get_setting( 'role' );
$role = get_role( $role_slug );
if ( is_null( $role ) ) {
$error = new \WP_Error( 'role_does_not_exist', 'Error: the role does not exist: ' . $role_slug );
$this->logging->log( $error->get_error_message(), __METHOD__, 'error' );
return $error;
}
return $role;
}
/**
* Returns the custom capability name that will be added to the role to indicate that it was created by TrustedLogin.
*
* @param string $ns The namespace of the vendor.
*
* @return string
*/
static public function get_capability_flag( $ns ) {
return str_replace( '{ns}', $ns, self::CAPABILITY_FLAG );
}
/**
* Creates the custom Support Role if it doesn't already exist
*
* @since 1.0.0
* @since 1.0.0 removed excluded_caps from generated role
*
* @param string $new_role_slug The slug for the new role (optional). Default: {@see SupportRole::get_name()}
* @param string $clone_role_slug The slug for the role to clone (optional). Default: {@see SupportRole::get_cloned_name()}.
*
* @return \WP_Role|\WP_Error Created/pre-existing role, if successful. WP_Error if failure.
*/
public function create( $new_role_slug = '', $clone_role_slug = '' ) {
if ( empty( $new_role_slug ) ) {
$new_role_slug = $this->get_name();
}
if ( ! is_string( $new_role_slug ) ) {
return new \WP_Error( 'new_role_slug_not_string', 'The slug for the new support role must be a string.' );
}
if ( empty( $clone_role_slug ) ) {
$clone_role_slug = $this->get_cloned_name();
}
if ( ! is_string( $clone_role_slug ) ) {
return new \WP_Error( 'cloned_role_slug_not_string', 'The slug for the cloned support role must be a string.' );
}
$role_exists = get_role( $new_role_slug );
if ( $role_exists ) {
$this->logging->log( 'Not creating user role; it already exists', __METHOD__, 'notice' );
return $role_exists;
}
$this->logging->log( 'New role slug: ' . $new_role_slug . ', Clone role slug: ' . $clone_role_slug, __METHOD__, 'debug' );
$old_role = get_role( $clone_role_slug );
if ( empty( $old_role ) ) {
return new \WP_Error( 'role_does_not_exist', 'Error: the role to clone does not exist: ' . $clone_role_slug );
}
$capabilities = $old_role->capabilities;
$add_caps = $this->config->get_setting( 'caps/add' );
foreach ( (array) $add_caps as $add_cap => $reason ) {
$capabilities[ $add_cap ] = true;
}
// These roles should never be assigned to TrustedLogin roles.
foreach ( self::$prevented_caps as $prevented_cap ) {
unset( $capabilities[ $prevented_cap ] );
}
/**
* @filter trustedlogin/{namespace}/support_role/display_name Modify the display name of the created support role
*/
$role_display_name = apply_filters( 'trustedlogin/' . $this->config->ns() . '/support_role/display_name',
// translators: %s is replaced with the name of the software developer (e.g. "Acme Widgets")
sprintf( esc_html__( '%s Support', 'trustedlogin' ), $this->config->get_setting( 'vendor/title' ) ),
$this
);
/**
* Add a flag to declare that this role was created by TrustedLogin.
* @used-by SupportRole::delete()
*/
$capabilities[ self::get_capability_flag( $this->config->ns() ) ] = true;
$new_role = add_role( $new_role_slug, $role_display_name, $capabilities );
if ( ! $new_role ){
return new \WP_Error(
'add_role_failed',
'Error: the role was not created using add_role()', compact(
"new_role_slug",
"capabilities",
"role_display_name"
)
);
}
$remove_caps = $this->config->get_setting( 'caps/remove' );
if ( ! empty( $remove_caps ) ){
foreach ( $remove_caps as $remove_cap => $description ){
$new_role->remove_cap( $remove_cap );
$this->logging->log( 'Capability '. $remove_cap .' removed from role.', __METHOD__, 'info' );
}
}
return $new_role;
}
/**
* @return bool|null Null: Role wasn't found; True: Removing role succeeded; False: Role wasn't deleted successfully.
*/
public function delete() {
$role_to_delete = get_role( $this->get_name() );
if ( ! $role_to_delete ) {
return null;
}
$capability_flag = self::get_capability_flag( $this->config->ns() );
// Don't delete roles that weren't created by TrustedLogin.
if ( ! $role_to_delete->has_cap( $capability_flag ) ) {
$this->logging->log( "Role " . $this->get_name() . " is missing the CAPABILITY_FLAG. It is not possible to determine that it was created by TrustedLogin; it will not be removed.", __METHOD__, 'error' );
return false;
}
// Sanity check: don't ever, for any reason, delete protected roles.
if ( in_array( $this->get_name(), self::$protected_roles ) ) {
$this->logging->log( "Role " . $this->get_name() . " is protected and cannot be removed.", __METHOD__, 'error' );
return false;
}
// Returns void; no way to tell if successful...
remove_role( $this->get_name() );
// So we manually check if it was removed successfully.
if( get_role( $this->get_name() ) ) {
$this->logging->log( "Role " . $this->get_name() . " was not removed successfully.", __METHOD__, 'error' );
return false;
}
$this->logging->log( "Role " . $this->get_name() . " removed.", __METHOD__, 'info' );
return true;
}
}