/[drupal]/contributions/modules/userprotect/userprotect.module
ViewVC logotype

Contents of /contributions/modules/userprotect/userprotect.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.54 - (show annotations) (download) (as text)
Fri Nov 6 02:26:42 2009 UTC (2 weeks, 5 days ago) by thehunmonkgroup
Branch: MAIN
CVS Tags: HEAD
Changes since 1.53: +2 -2 lines
File MIME type: text/x-php
adding .pot file, fix bad t\(\) call.
1 <?php
2
3 // $Id: userprotect.module,v 1.53 2009/11/04 13:37:14 thehunmonkgroup Exp $
4
5 /**
6 * Implement hook_help().
7 *
8 * Returns various help texts.
9 */
10 function userprotect_help($path, $arg) {
11 switch ($path) {
12 case 'admin/config/people/userprotect':
13 case 'admin/config/people/userprotect/protected_users':
14 $output = t('These settings override any <a href="!protected_roles">role-based protections</a> for the user in question. For more information on how to configure userprotect settings, see the <a href="!help">help section</a>.', array('!help' => url('admin/help/userprotect'), '!protected_roles' => url('admin/config/people/userprotect/protected_roles')));
15 return $output;
16 case 'admin/config/people/userprotect/protected_roles':
17 $output = t('These settings add protections to any user who is in the specified role. They are overridden by any <a href="!protected_users">per-user protections</a> for the user in question. For more information on how to configure userprotect settings, see the <a href="!help">help section</a>.', array('!help' => url('admin/help/userprotect'), '!protected_users' => url('admin/config/people/userprotect/protected_users')));
18 return $output;
19 case 'admin/config/people/userprotect/administrator_bypass':
20 $output = t('These settings add bypasses to any user who has the \'administer users\' permission. They override the <a href="!protection_defaults">defaults</a> for the user in question. For more information on how to configure userprotect settings, see the <a href="!help">help section</a>.', array('!help' => url('admin/help/userprotect'), '!protection_defaults' => url('admin/config/people/userprotect/protection_defaults')));
21 return $output;
22 case 'admin/config/people/userprotect/protection_defaults':
23 $output = t('Set global default protection values here. For more information on how to configure userprotect settings, see the <a href="!help">help section</a>.', array('!help' => url('admin/help/userprotect')));
24 return $output;
25 case 'admin/help#userprotect':
26
27 $output = t('<p>This module provides various editing protection for users.
28 The protections can be specific to a user, or applied to all users in a role.
29 The following protections are supported:</p>
30 <ul>
31 <li>username</li>
32 <li>e-mail address</li>
33 <li>password</li>
34 <li>status changes</li>
35 <li>roles</li>
36 <li>cancellation</li>
37 <li>OpenID identities (both adding and deleting)</li>
38 <li>all edits (any accessed via user/X/edit)</li>
39 </ul>
40
41 <p>When a protection is enabled for a specified user (or the protection is
42 enabled because the user belongs to a role that has the protection), it
43 prevents the editing operation in question that anyone might try to perform
44 on the user -- unless an administrator who is permitted to bypass the protection
45 is editing the specified user. The module will protect fields by disabling
46 them at user/X/edit.<p>
47
48 <p>User administrators may be configured to bypass specified protections, on either
49 a global or per-administrator basis.</p>
50
51 <p>These protections are valid both when trying to edit the user directly from their
52 user/X/edit page, or using the <a href="!admin_people">mass user editing
53 operations</a>.</p>
54
55 <p>The module also provides protection at the paths user/X/edit and user/X/cancel,
56 should anyone try to visit those paths directly.</p>
57
58 <p><em>Note: this module is compatible with the <a href="!roleassign">
59 RoleAssign</a> module.</em></p>
60
61 <h4>SETTINGS:</h4>
62
63 <p>At the <a href="!userprotect_settings">User Protect settings page</a>,
64 you\'ll find the settings for the module. When the module is initially enabled,
65 the default settings are such:</p>
66
67 <ul>
68 <li>User administrators bypass all protections.</li>
69 <li>The root user specifically bypasses all protections.</li>
70 <li>The anonymous user (uid 0) and root user (uid 1) are protected
71 from all edits, cancellation, and OpenID operations.</li>
72 <li>All role protections are disabled.</li>
73 <li>The \'change own e-mail\', \'change own password\' and \change own openid\'
74 permissions are enabled for authenticated users in the <a href="!permissions">
75 User Protect permissions settings</a>.
76 </ul>
77
78 <p>This effectively amounts to no protections. It is suggested that you turn off
79 as many default administrator bypass settings as possible, and set bypass settings
80 for specific user administrators -- this allows you to take advantage
81 of the status, roles, cancellation, openid and edit protections in a meaningful
82 way. Because of the per-user bypass/protection settings for the anonymous and
83 root user, this will also begin protecting those users, without compromising
84 the root user\'s access to the entire site.</p>
85
86 <p>Important note: In order to protect a user from cancellation (by visiting
87 user/X/cancel directly) and/or OpenID edits (by visiting user/X/openid
88 directly), you must enable the \'cancel\' and/or \'openid\' protection specifically.
89 Enabling \'all account edits\' does not enable these protections!</p>
90
91 <p>Also note that this module only provides protection against actions via the website
92 interface--operations that a module takes directly are not protected! This module
93 should play well with other contributed modules, but there is no guarantee that all
94 protections will remain intact if you install modules outside of the drupal core
95 installation.</p>
96
97 <h4>ADDING PROTECTIONS FOR A SINGLE USER:</h4>
98
99 <p>This is done at <a href="!protected_users">per-user protections</a>.
100 Any time a user is added for protection, they will initially receive the
101 <a href="!protection_defaults">default protections</a>.</p>
102
103
104 <h4>ADDING PROTECTIONS FOR ROLES:</h4>
105
106 <p>This is done at <a href="!protected_roles">role-based protections</a>.
107 <em>Be cautious</em> about adding protections by role, or you can lock out users
108 from things unintentionally!</p>
109
110 <p>In particular, note the if you enable role protections for a specific role, and
111 you have no bypasses enabled, you\'ve effectively locked out any role editing for
112 that role by anybody, unless you come back to the settings page and disable the role
113 protection!</p>
114
115 <h4>ADDING ADMINISTRATOR BYPASS RULES:</h4>
116
117 <p>One of the more powerful features of the module is administrator bypass
118 Any user that has been granted the \'administer users\' permission can
119 be configured to bypass any protection, either via the
120 <a href="!protection_defaults">default administrator bypass settings</a>,
121 or via a <a href="!administrator_bypass">per-administrator setting</a>.
122 If a bypass is enabled for a user administrator, they will be given editing rights
123 on that protection regardless if it is enabled for a single user or an entire role.</p>
124
125 <p>Note that the per-administrator bypass settings override the default bypass
126 settings.</p>
127
128 <h4>DEFAULT PROTECTION SETTINGS:</h4>
129
130 <p>Set the <a href="!protection_defaults">default protections</a> for newly protected users.
131 In addition, you can enable the auto-protect feature, which will automatically
132 add the default protections to any newly created user accounts, and set default
133 bypass options for all user administrators.</p>
134
135
136 <h4>HOW THE MODULE DETERMINES A PROTECTION:</h4>
137
138 <p>In order to properly use User Protect, it\'s important to understand how the
139 module determines if a specified field is to be protected. Here is the basic
140 logic:</p>
141 <ol>
142 <li>If the current user is a user administrator, check if they have
143 per-administrator bypass settings. If so, then check to see if they are allowed
144 to bypass the protection. If so, then stop the checks and allow editing
145 of the field.</li>
146 <li>If not, then if the current user is a user administrator, check if the
147 default administrator bypass is enabled for the protection in question. If
148 so, then stop the checks and allow editing of the field.
149 <li>If not, check if the user is editing their own account. If so, determine
150 the protections for e-mail, password, and openid by examining the userprotect permissions
151 for \'change own e-mail\', \'change own password\' and \'change own openid\',
152 then continue with the rest of the checks below.
153 <li>If not, check if the protection is set for the individual user being edited.
154 If so, then stop the checks here, and prevent editing of the field (this effectively
155 means that individual protections override role protections).</li>
156 <li>If not, then examine all the roles for the user being edited. If any of
157 those roles have the protection enabled, then prevent editing of the field.</li>
158 <li>If not, then allow the field to be edited.</li>
159 </ol>
160 </p>', array('!admin_people' => url('admin/people'), '!userprotect_settings' => url('admin/config/people/userprotect/protected_users'), '!protected_users' => url('admin/config/people/userprotect/protected_users'), '!protected_roles' => url('admin/config/people/userprotect/protected_roles'), '!administrator_bypass' => url('admin/config/people/userprotect/administrator_bypass'), '!protection_defaults' => url('admin/config/people/userprotect/protection_defaults'), '!roleassign' => url('http://drupal.org/project/roleassign', array('attributes' => array('target' => 'X'), 'absolute' => TRUE)), '!permissions' => url('admin/config/people/permissions', array('fragment' => 'module-userprotect'))));
161 return $output;
162 }
163 }
164
165 /**
166 * Implement hook_form_alter().
167 */
168 function userprotect_form_alter(&$form, &$form_state, $form_id) {
169
170 switch ($form_id) {
171
172 // These are complex cases, and are best handled by manipulating the form values
173 // in a custom validate function.
174 case 'user_admin_account':
175 case 'user_multiple_cancel_confirm':
176 // Ensure an array.
177 $form['#validate'] = isset($form['#validate']) ? $form['#validate'] : array();
178 array_unshift($form['#validate'], 'userprotect_user_admin_account_validate');
179 break;
180 case 'openid_user_add':
181 case 'openid_user_delete_form':
182 $account = user_load(arg(1));
183 $protected = array();
184 if (!userprotect_check_bypass('up_openid') && userprotect_get_user_protection($account, 'up_openid')) {
185 switch ($form_id) {
186 case 'openid_user_add':
187 if (isset($form['openid_identifier'])) {
188 $form['openid_identifier']['#disabled'] = TRUE;
189 $form['submit']['#disabled'] = TRUE;
190 }
191 break;
192 case 'openid_user_delete_form':
193 if (isset($form['actions']['submit'])) {
194 $form['actions']['submit']['#disabled'] = TRUE;
195 $form['confirm']['#value'] = 0;
196 }
197 break;
198 }
199 $protected['up_openid'] = TRUE;
200 }
201 userprotect_form_display_protections($account, $protected);
202 break;
203 }
204 }
205
206
207 /**
208 * Implement hook_form_user_profile_form_alter().
209 */
210 function userprotect_form_user_profile_form_alter(&$form, &$form_state) {
211
212 // For each of the fields, first check if any of the user's roles are protecting
213 // it, then check if the user themselves is protected from it. if either is TRUE,
214 // then disable the field, and mark a fixed form value so it will be properly submitted.
215 $account = user_load(arg(1));
216 $protected = array();
217 if (isset($form['account']['name']) && !userprotect_check_bypass('up_name') && userprotect_get_user_protection($account, 'up_name')) {
218 // If for some reason this field has no initial value, then don't protect it.
219 if ($account->name) {
220 $form['account']['name']['#disabled'] = TRUE;
221 $form['account']['name']['#value'] = $account->name;
222 $protected['up_name'] = TRUE;
223 }
224 }
225 if (isset($form['account']['mail']) && !userprotect_check_bypass('up_mail') && userprotect_get_user_protection($account, 'up_mail')) {
226 // If for some reason this field has no initial value, then don't protect it.
227 if ($account->mail) {
228 $form['account']['mail']['#disabled'] = TRUE;
229 $form['account']['mail']['#value'] = $account->mail;
230 $protected['up_mail'] = TRUE;
231 }
232 }
233 // Password is an exception, as it needs no value, Just unset it, as
234 // there's no need to display two empty boxes that are disabled.
235 if (isset($form['account']['pass']) && !userprotect_check_bypass('up_pass') && userprotect_get_user_protection($account, 'up_pass')) {
236 unset($form['account']['pass']);
237 $protected['up_pass'] = TRUE;
238 }
239 if (isset($form['account']['status']) && !userprotect_check_bypass('up_status') && userprotect_get_user_protection($account, 'up_status')) {
240 $form['account']['status']['#disabled'] = TRUE;
241 $form['account']['status']['#value'] = $account->status;
242 $protected['up_status'] = TRUE;
243 }
244 // Special hack for RoleAssign module compatibility.
245 if (isset($form['account']['roleassign_roles'])) {
246 $roles = 'roleassign_roles';
247 }
248 else {
249 $roles = 'roles';
250 }
251 // Roles is a special case, since it's a tree'd item that needs values.
252 // We'll handle that in a custom validation function. Also here we slip
253 // the user's account info into the form so it's available to gleen the role
254 // info from.
255 if (isset($form['account'][$roles]) && !userprotect_check_bypass('up_roles') && userprotect_get_user_protection($account, 'up_roles')) {
256 $form['account'][$roles]['#disabled'] = TRUE;
257 // Ensure an array.
258 $form['account'][$roles]['#element_validate'] = isset($form['account'][$roles]['#element_validate']) ? $form['account'][$roles]['#element_validate'] : array();
259 array_unshift($form['account'][$roles]['#element_validate'], 'userprotect_user_edit_fields_validate');
260 $form_state['userprotect']['account'] = $account;
261 $form_state['userprotect']['field'] = 'roles';
262 $protected['up_roles'] = TRUE;
263 }
264
265 // At this point, we only need the userprotect-specific validation if the
266 // current user and the edited user are not the same.
267 if (isset($form['cancel']) && ($GLOBALS['user']->uid != $account->uid)) {
268 // Nothing special for cancel--just disable.
269 if (!userprotect_check_bypass('up_cancel') && userprotect_get_user_protection($account, 'up_cancel')) {
270 $form['cancel']['#disabled'] = TRUE;
271 $protected['up_cancel'] = TRUE;
272 }
273 }
274 userprotect_form_display_protections($account, $protected);
275 }
276
277 /**
278 * Custom validation function for complex field protections.
279 */
280 function userprotect_user_edit_fields_validate($form, &$form_state) {
281 $account = $form_state['userprotect']['account'];
282 $field = $form_state['userprotect']['field'];
283
284 switch ($field) {
285 case 'roles':
286 // Authenticated user isn't a valid checked item.
287 unset($account->roles[DRUPAL_AUTHENTICATED_RID]);
288 // Add values for all role checkboxes that are valid roles for this user.
289 foreach ($account->roles as $rid => $role) {
290 if (isset($form[$rid])) {
291 form_set_value($form[$rid], 1, $form_state);
292 }
293 }
294 break;
295 }
296 }
297
298 /**
299 * Implement hook_menu().
300 */
301 function userprotect_menu() {
302 $items = array();
303 $admin = array('administer userprotect');
304
305 // Admin page link.
306 $items['admin/config/people/userprotect'] = array(
307 'title' => 'User Protect',
308 'page callback' => 'drupal_get_form',
309 'page arguments' => array('userprotect_protected_users'),
310 'access callback' => 'user_access',
311 'access arguments' => $admin,
312 'description' => 'Protect inidividual users and/or roles from editing operations.',
313 );
314 // Default tab.
315 $items['admin/config/people/userprotect/protected_users'] = array(
316 'title' => 'Protected users',
317 'type' => MENU_DEFAULT_LOCAL_TASK,
318 'access callback' => 'user_access',
319 'access arguments' => $admin,
320 'weight' => 1,
321 );
322 // Protected roles tab.
323 $items['admin/config/people/userprotect/protected_roles'] = array(
324 'title' => 'Protected roles',
325 'page callback' => 'drupal_get_form',
326 'page arguments' => array('userprotect_protected_roles'),
327 'access callback' => 'user_access',
328 'access arguments' => $admin,
329 'type' => MENU_LOCAL_TASK,
330 'weight' => 2,
331 );
332 // Administrator bypass tab.
333 $items['admin/config/people/userprotect/administrator_bypass'] = array(
334 'title' => 'Administrator bypass',
335 'page callback' => 'drupal_get_form',
336 'page arguments' => array('userprotect_administrator_bypass'),
337 'access callback' => 'user_access',
338 'access arguments' => $admin,
339 'type' => MENU_LOCAL_TASK,
340 'weight' => 3,
341 );
342 // Default settings.
343 $items['admin/config/people/userprotect/protection_defaults'] = array(
344 'title' => 'Protection defaults',
345 'page callback' => 'drupal_get_form',
346 'page arguments' => array('userprotect_protection_defaults'),
347 'access callback' => 'user_access',
348 'access arguments' => $admin,
349 'type' => MENU_LOCAL_TASK,
350 'weight' => 4,
351 );
352 // Remove a user from being protected.
353 $items['userprotect/delete/%user'] = array(
354 'title' => 'Delete protected user',
355 'page callback' => 'drupal_get_form',
356 'page arguments' => array('userprotect_protected_users_delete_form', 2, 3),
357 'type' => MENU_CALLBACK,
358 'access callback' => 'user_access',
359 'access arguments' => $admin,
360 );
361
362 return $items;
363 }
364 /**
365 * Implement hook_menu_alter().
366 *
367 * Since we also have to guard against menu items being called
368 * directly from a URL, this page check is necessary. The checks
369 * are invoked for user/x/edit and user/x/cancel, and replace user
370 * modules's default access checks.
371 */
372 function userprotect_menu_alter(&$callbacks) {
373 $callbacks['user/%user/edit']['access callback'] = 'userprotect_user_edit_access';
374 $callbacks['user/%user/cancel']['access callback'] = 'userprotect_user_cancel_access';
375 }
376
377 /**
378 * Access callback for user edit pages.
379 *
380 * This replaces user_edit_access from user.module.
381 *
382 * @param $account
383 * An object representing the user to be edited.
384 */
385 function userprotect_user_edit_access($account) {
386 // Perform core's access check.
387 if ((($GLOBALS['user']->uid == $account->uid) || user_access('administer users')) && $account->uid > 0) {
388 // Check to see if the user's roles are protecting edits, or the user
389 // account itself is protected.
390 if (!userprotect_check_bypass('up_edit') && userprotect_get_user_protection($account, 'up_edit')) {
391 // If so, and we're at /user/X/edit, set a message.
392 if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'edit') {
393 drupal_set_message(t('%user is currently being protected from any edits.', array('%user' => $account->name)), 'error');
394 }
395 return FALSE;
396 }
397 else {
398 return TRUE;
399 }
400 }
401 else {
402 return FALSE;
403 }
404 }
405
406 /**
407 * Access callback for user cancel pages.
408 *
409 * This replaces the logic from user.module.
410 *
411 * @param $account
412 * An object representing the user to be cancelled.
413 */
414 function userprotect_user_cancel_access($account) {
415 // Perform core's access check.
416 if (((($GLOBALS['user']->uid == $account->uid) && user_access('cancel account')) || user_access('administer users')) && $account->uid > 0) {
417 // At this point, we only need the userprotect-specific validation if:
418 // 1. The current user and the edited user are not the same.
419 // 2. The current user is a user administrator.
420 if (($GLOBALS['user']->uid != $account->uid) && user_access('administer users')) {
421 // Check to see if the user's roles are protecting cancellation, or the user
422 // account itself is protected.
423 if (!userprotect_check_bypass('up_cancel') && userprotect_get_user_protection($account, 'up_cancel')) {
424 // If so, and we're at /user/X/cancel, set a message.
425 if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'cancel') {
426 drupal_set_message(t('%user is currently being protected from cancellation.', array('%user' => $account->name)), 'error');
427 }
428 return FALSE;
429 }
430 }
431 else {
432 return TRUE;
433 }
434 }
435 else {
436 return FALSE;
437 }
438 }
439
440 /**
441 * Implement hook_permission().
442 */
443 function userprotect_permission() {
444 return array(
445 'change own e-mail' => array(
446 'title' => t('Change own e-mail'),
447 'description' => t('Allow users to edit their own e-mail address.'),
448 ),
449 'change own password' => array(
450 'title' => t('Change own password'),
451 'description' => t('Allow users to edit their own password.'),
452 ),
453 'change own openid' => array(
454 'title' => t('Change own OpenID'),
455 'description' => t('Allow users to edit their own OpenID identities.'),
456 ),
457 'administer userprotect' => array(
458 'title' => t('Administer User Protect'),
459 'description' => t('Set up access rules for user administrators for various user-related edits.'),
460 ),
461 );
462 }
463
464 /**
465 * Implement hook_theme().
466 */
467 function userprotect_theme() {
468 return array(
469 'userprotect_admin_role_table' => array(
470 'render element' => 'form',
471 ),
472 'userprotect_protections_bypass' => array(
473 'render element' => 'form',
474 ),
475 );
476 }
477
478 /**
479 * Implement hook_user_insert().
480 */
481 function userprotect_user_insert(&$edit, $account) {
482
483 // A new user is being added. If auto-protect is enabled, then add protection.
484 if (variable_get('userprotect_autoprotect', FALSE)) {
485 userprotect_add_user($account->uid, 'user');
486 $protected = array_filter(variable_get('userprotect_protection_defaults', userprotect_user_protection_defaults()));
487 drupal_set_message(userprotect_display_protections($account, $protected));
488 }
489 }
490
491
492 /**
493 * Implement hook_user_cancel().
494 */
495 function userprotect_user_cancel($edit, $account, $method) {
496 switch ($method) {
497 // Remove a deleted user from the protections table.
498 case 'user_cancel_reassign':
499 case 'user_cancel_delete':
500 db_delete('userprotect')
501 ->condition('uid', $account->uid)
502 ->execute();
503 break;
504 }
505 }
506
507 /**
508 * Builds a table of protected users, and their protections.
509 *
510 * @return A form array representing the table.
511 */
512 function userprotect_protected_users() {
513 return userprotect_protections_bypass('user');
514 }
515
516 /**
517 * Builds a table of user admin bypass values.
518 *
519 * @return A form array representing the table.
520 */
521 function userprotect_administrator_bypass() {
522 return userprotect_protections_bypass('admin');
523 }
524
525 /**
526 * Helper funtion. Builds tables for protected users and admin bypass.
527 *
528 * @return A form array representing the table.
529 */
530 function userprotect_protections_bypass($type) {
531
532 // Build the header.
533 $header = array(array('data' => t('User'), 'field' => 'name', 'sort' => 'asc'));
534
535 $protect_columns = userprotect_get_protection_display();
536 foreach ($protect_columns as $field => $data) {
537 $header[] = array('data' => $data, 'field' => $field);
538 }
539 $header[] = array('data' => t('Operations'));
540
541 $query = db_select('userprotect', 'up');
542 $query->innerJoin('users', 'u', 'up.uid = u.uid');
543 $query->condition('up.up_type', $type);
544
545 $count_query = clone $query;
546 $count_query->addExpression('COUNT(DISTINCT u.uid)');
547
548 $query = $query->extend('PagerDefault')->extend('TableSort');
549
550 // These are all protection fields in the database.
551 $protection_fields = array_keys(userprotect_get_protection_display());
552
553 // Grab the protected users.
554 $query
555 ->fields('up', $protection_fields)
556 ->fields('u', array('uid', 'name'))
557 ->limit(25)
558 ->orderByHeader($header)
559 ->setCountQuery($count_query);
560 $protected_users = $query->execute();
561
562 // Set some initial values.
563 $delete = t('delete');
564 $options = array();
565
566 // These are all available protections.
567 $protections = array_keys(userprotect_user_protection_defaults());
568
569 // Pass in the header and list of protections to the form so they'll be available
570 // to the theming function.
571 $form = array();
572 $form['protection']['#tree'] = TRUE;
573 $form['#header'] = $header;
574 $form['#protections'] = $protections;
575 $form['#submit'][] = 'userprotect_protections_bypass_submit';
576 $form['#theme'] = 'userprotect_protections_bypass';
577
578 // Build the checkboxes options.
579 foreach ($protections as $protection) {
580 $options[$protection] = '';
581 }
582
583 // For each protected user, build their table row.
584 foreach ($protected_users as $protected_user) {
585 $defaults = array();
586
587 $user = user_load($protected_user->uid);
588
589 $form['user'][$user->uid]['uid'] = array(
590 '#type' => 'value',
591 '#value' => $user->uid
592 );
593 $form[$user->uid]['name'] = array(
594 '#theme' => 'username',
595 '#account' => $user,
596 );
597 $form[$user->uid]['operations'] = array('#markup' => $user->uid ? l($delete, "userprotect/delete/$user->uid/$type") : '');
598 // Build the protections for the user row.
599 foreach ($protections as $protection) {
600 if ($protected_user->$protection) {
601 $defaults[] = $protection;
602 }
603 }
604
605 // The checkboxes for this user.
606 $form['protection'][$user->uid] = array(
607 '#type' => 'checkboxes',
608 '#options' => $options,
609 '#default_value' => $defaults,
610 );
611 }
612
613 // An autocomplete field to add new users for protection.
614 // This needs a custom validation function to check the user
615 // to be added.
616 $form['up_add'] = array(
617 '#type' => 'textfield',
618 '#maxlength' => 60,
619 '#autocomplete_path' => 'user/autocomplete',
620 '#element_validate' => array('userprotect_up_add_validate'),
621 '#userprotect_type' => $type,
622 );
623 $form['up_add_text'] = array('#markup' => t('Add user'),);
624 $form['userprotect_type'] = array(
625 '#type' => 'value',
626 '#value' => $type,
627 );
628
629 $form['pager'] = array(
630 '#theme' => 'pager',
631 '#tags' => NULL,
632 );
633
634 $form['submit'] = array(
635 '#type' => 'submit',
636 '#value' => t('Save')
637 );
638 return $form;
639 }
640
641 /**
642 * Themes the protected users table.
643 *
644 * @param $form The form to theme.
645 * @return An HTML string representing the constructed form.
646 */
647 function theme_userprotect_protections_bypass($variables) {
648 $form = $variables['form'];
649 $rows = array();
650 // Buikd the row for each user.
651 foreach (element_children($form['user']) as $uid) {
652 $row = array();
653 $row[] = drupal_render($form[$uid]['name']);
654 // Build the protections for the user row.
655 foreach ($form['#protections'] as $protection) {
656 $row[] = drupal_render($form['protection'][$uid][$protection]);
657 }
658 $row[] = drupal_render($form[$uid]['operations']);
659 $rows[] = $row;
660 }
661
662 // Add the last row with the add textfield.
663 $rows[] = array(
664 array('data' => drupal_render($form['up_add']), 'colspan' => strval(count($form['#header']) - 1)),
665 array('data' => drupal_render($form['up_add_text']), 'colspan' => '1')
666 );
667
668 // Theme the table.
669 $output = theme('table', array('header' => $form['#header'], 'rows' => $rows));
670 $output .= drupal_render_children($form);
671
672 return $output;
673 }
674
675 /**
676 * Custom validation function for adding a user for protection.
677 */
678 function userprotect_up_add_validate($form, &$form_state) {
679 // If a user has been submitted
680 if ($username = $form['#value']) {
681 $type = $form['#userprotect_type'];
682 // If the user is valid, and they are not already being protected...
683 if ($uid = db_query("SELECT uid FROM {users} WHERE name = :name", array(
684 ':name' => $username,
685 ))->fetchField()) {
686 if (!db_query("SELECT uid FROM {userprotect} WHERE uid = :uid AND up_type = :up_type", array(
687 ':uid' => $uid,
688 ':up_type' => $type,
689 ))->fetchField()) {
690 if ($uid != 1 && $type == 'admin' && !db_query("SELECT ur.uid FROM {users_roles} ur INNER JOIN {role_permission} rp ON ur.rid = rp.rid WHERE rp.permission = :permission AND ur.uid = :uid", array(
691 ':permission' => 'administer users',
692 ':uid' => $uid,
693 ))->fetchField()) {
694 form_set_error('up_add', t('%user does not have user administration privileges.', array('%user' => $username)));
695 }
696 else {
697 // Transform the username into a uid.
698 form_set_value($form, $uid, $form_state);
699 }
700 }
701 // Can't add a user twice
702 else {
703 form_set_error('up_add', t('%user is already on this list.', array('%user' => $username)));
704 }
705
706 }
707 // Can't add a user that doesn't exist.
708 else {
709 form_set_error('up_add', t('The username is invalid.'));
710 }
711 }
712 }
713
714 /**
715 * Processes the submitted user protection form.
716 */
717 function userprotect_protections_bypass_submit($form, &$form_state) {
718
719 $type = $form_state['values']['userprotect_type'];
720
721 // A user was added, so add them to the protected users table.
722 if ($uid = $form_state['values']['up_add']) {
723 userprotect_add_user($uid, $type);
724 $username = userprotect_get_username($uid);
725 if ($type == 'user') {
726 drupal_set_message(t('%user is now protected.', array('%user' => $username)));
727 }
728 elseif ($type == 'admin') {
729 drupal_set_message(t('%user now has bypass capabilities matching the default protections for newly protected users.', array('%user' => $username)));
730 }
731 }
732 if (is_array($form_state['values']['protection'])) {
733 // Load the defaults as a reference to all protections.
734 $protections_values = userprotect_user_protection_defaults();
735 // Loop through each user.
736 foreach ($form_state['values']['protection'] as $uid => $protections) {
737 $fields = array();
738 // Loop through the submitted user's protections, setting them enabled or
739 // disabled as appropriate for the update query. Note: $protection is
740 // a module generated string, so it's safe.
741 foreach ($protections_values as $protection => $value) {
742 $fields[$protection] = $protections[$protection] ? 1 : 0;
743 }
744
745 // Update the user's protections.
746 db_update('userprotect')
747 ->fields($fields)
748 ->condition('uid', $uid)
749 ->condition('up_type', $type)
750 ->execute();
751 }
752 if ($type == 'user') {
753 drupal_set_message(t('Protection settings updated.'));
754 }
755 elseif ($type == 'admin') {
756 drupal_set_message(t('Bypass settings updated.'));
757 }
758 }
759 }
760
761 /**
762 * Menu callback. Removes a user from being protected, or removes an
763 * administrator bypass.
764 */
765 function userprotect_protected_users_delete_form($form, &$form_state, $account, $type = 'user') {
766 if ($type == 'user') {
767 $type_display = t('protections');
768 $admin_page = 'protected_users';
769 }
770 elseif ($type == 'admin') {
771 $type_display = t('administrator bypass');
772 $admin_page = 'administrator_bypass';
773 }
774 $form_state['userprotect']['account'] = $account;
775 $form_state['userprotect']['type'] = $type;
776 $form_state['userprotect']['type_display'] = $type_display;
777 $form_state['userprotect']['admin_page'] = $admin_page;
778 return confirm_form(array(), t('Are you sure you want to delete the individual !type for %user?', array('!type' => $type_display, '%user' => $account->name)), "admin/config/people/userprotect/$admin_page");
779 }
780
781 /**
782 * Submit function for the delete confirmation form.
783 */
784 function userprotect_protected_users_delete_form_submit($form, &$form_state) {
785 $account = $form_state['userprotect']['account'];
786 $type = $form_state['userprotect']['type'];
787 $type_display = $form_state['userprotect']['type_display'];
788 $admin_page = $form_state['userprotect']['admin_page'];
789 db_delete('userprotect')
790 ->condition('uid', $account->uid)
791 ->condition('up_type', $type)
792 ->execute();
793 if ($type == 'user') {
794 drupal_set_message(t('%user is no longer protected.', array('%user' => $account->name)));
795 }
796 elseif ($type == 'admin') {
797 drupal_set_message(t('%user is no longer enabled for bypass.', array('%user' => $account->name)));
798 }
799
800 $form_state['redirect'] = "admin/config/people/userprotect/$admin_page";
801 }
802
803 /**
804 * Builds a form for the role protection settings.
805 *
806 * @return An array representing the form.
807 */
808 function userprotect_protected_roles() {
809
810 $form = array();
811
812 // Get the list of all protections, and the current default settings.
813 $options = userprotect_get_protection_display();
814
815 // Build the header.
816 $header = array(t('Role'));
817 foreach ($options as $field => $data) {
818 $header[] = $data;
819 }
820
821 // Grab all roles but the anonymous role, and grab the current default settings.
822 $roles = user_roles(TRUE);
823 $protected_roles = variable_get('userprotect_role_protections', array());
824
825 // This is a complete list of protections for reference.
826 $protections = array_keys(userprotect_user_protection_defaults());
827
828 // Pass in the header and protections so they're available for the theme function.
829 // Also, we want this as one big array to save in the variables table, so tree it.
830 $form['role_table']['#header'] = $header;
831 $form['role_table']['#theme'] = 'userprotect_admin_role_table';
832 $form['role_table']['#protections'] = $protections;
833 $form['role_table']['userprotect_role_protections']['#tree'] = TRUE;
834
835 // Build a row for each role.
836 foreach ($roles as $rid => $role) {
837 $form['role_table']['userprotect_role_protections'][$rid]['name'] = array('#markup' => $role);
838 // Build protections for the row.
839 foreach ($protections as $protection) {
840 $form['role_table']['userprotect_role_protections'][$rid][$protection] = array(
841 '#type' => 'checkbox',
842 );
843 if (isset($protected_roles[$rid][$protection])) {
844 $form['role_table']['userprotect_role_protections'][$rid][$protection]['#default_value'] = $protected_roles[$rid][$protection];
845 }
846 }
847 }
848
849 return system_settings_form($form);
850 }
851
852 /**
853 * Builds a form for the userprotect default settings.
854 *
855 * @return An array representing the form.
856 */
857 function userprotect_protection_defaults() {
858
859 // Get the list of all protections, and the current default settings.
860 $options = userprotect_get_protection_display();
861 $current_defaults = variable_get('userprotect_protection_defaults', userprotect_user_protection_defaults());
862
863 // Transform the defaults into proper checkboxes defaults.
864 $defaults = array_keys(array_filter($current_defaults));
865
866 // A set of checkboxes that lists the default protection settings.
867 $form['userprotect_protection_defaults'] = array(
868 '#type' => 'checkboxes',
869 '#title' => t('User protection defaults'),
870 '#description' => t('The selected protections will be assigned to users when they are first added for protection.'),
871 '#options' => $options,
872 '#default_value' => $defaults,
873 );
874 // A checkbox to enable the auto-protect functionality.
875 $form['userprotect_autoprotect'] = array(
876 '#type' => 'checkbox',
877 '#title' => t('Auto-protect new users'),
878 '#description' => t('If selected, all newly created users will automatically be protected and assigned the default protections above.'),
879 '#default_value' => variable_get('userprotect_autoprotect', FALSE),
880 );
881 // A set of checkboxes that lists the default protection settings.
882 $form['userprotect_administrator_bypass_defaults'] = array(
883 '#type' => 'checkboxes',
884 '#title' => t('Administrator bypass defaults'),
885 '#description' => t('If selected, all users with the \'administer users\' permission will be allowed to bypass the protection<br \><em>Note: this default setting is overridden by the <a href="!per_user_bypass">per-user administrator bypass settings</a>.</em>.', array('!per_user_bypass' => url('admin/config/people/userprotect/administrator_bypass'))),
886 '#options' => $options,
887 '#default_value' => variable_get('userprotect_administrator_bypass_defaults', userprotect_administrator_bypass_defaults()),
888 );
889
890 return system_settings_form($form);
891 }
892
893 /**
894 * Themes the role protections table.
895 *
896 * @param $form The form for the table.
897 * @return An HTML string representing the table.
898 */
899 function theme_userprotect_admin_role_table($variables) {
900 $form = $variables['form'];
901 $rows = array();
902 // Build a row for each role
903 foreach (element_children($form['userprotect_role_protections']) as $rid) {
904 $row = array();
905 $row[] = drupal_render($form['userprotect_role_protections'][$rid]['name']);
906 // Build the protections for each row.
907 foreach ($form['#protections'] as $protection) {
908 $row[] = drupal_render($form['userprotect_role_protections'][$rid][$protection]);
909 }
910 $rows[] = $row;
911 }
912
913 // Theme the table.
914 $output = t('<h3>Protections by role</h3>');
915 $output .= theme('table', array('header' => $form['#header'], 'rows' => $rows));
916 $output .= t('<div class="description">Setting a protection for a role will enable that protection for all users in the role.</div>');
917 $output .= drupal_render_children($form);
918
919 return $output;
920 }
921
922 /**
923 * Custom validation function for protecting users from the user
924 * administration operations.
925 */
926 function userprotect_user_admin_account_validate($form, &$form_state) {
927
928 // Get the checked users, and the operation name.
929 $uids = array_filter($form_state['values']['accounts']);
930 $operation_rid = explode('-', $form_state['values']['operation']);
931 $operation = $operation_rid[0];
932
933 // Perform the check for each submitted user.
934 foreach ($uids as $uid) {
935 $account = user_load($uid);
936
937 switch ($operation) {
938 case 'block':
939 case 'unblock':
940 // Check to see if any of the user's roles are protected from status changes,
941 // then check to see if the user is protected.
942 if (!userprotect_check_bypass('up_status') && userprotect_get_user_protection($account, 'up_status')) {
943 // If so, then unset the checked user so they will not be processed, and display a warning.
944 form_set_value($form['accounts'][$uid], 0, $form_state);
945 drupal_set_message(t('%user is protected from status changes, and was not updated.', array('%user' => $account->name)), 'error');
946 unset($uids[$uid]);
947 }
948 break;
949 case 'cancel':
950 // Check to see if any of the user's roles are protected from cancellation,
951 // then check to see if the user is protected.
952 if (!userprotect_check_bypass('up_cancel') && userprotect_get_user_protection($account, 'up_cancel')) {
953 // If so, then unset the checked user so they will not be processed, and display a warning.
954 // Note that the array element has to be completely removed here in order to prevent the
955 // user from being cancelled, due to the nature of the mass cancellation callback.
956 unset($form_state['values']['accounts'][$uid]);
957 drupal_set_message(t('%user is protected from cancellation, and was not cancelled.', array('%user' => $account->name)), 'error');
958 unset($uids[$uid]);
959 }
960 break;
961 case 'add_role':
962 case 'remove_role':
963 // RoleAssign module compatibility hack.
964 case 'roleassign_add_role':
965 case 'roleassign_remove_role':
966 // Check to see if any of the user's roles are protected from status changes,
967 // then check to see if the user is protected.
968 if (!userprotect_check_bypass('up_roles') && userprotect_get_user_protection($account, 'up_roles')) {
969 // If so, then unset the checked user so they will not be processed, and display a warning.
970 form_set_value($form['accounts'][$uid], 0, $form_state);
971 drupal_set_message(t('%user is protected from role changes, and was not updated.', array('%user' => $account->name)), 'error');
972 unset($uids[$uid]);
973 }
974 break;
975 }
976 }
977
978 // If we've unset all of the users that were checked, then don't continue
979 // with the form processing.
980 if (!count($uids)) {
981 drupal_set_message('No users selected.', 'error');
982 drupal_goto('admin/people');
983 }
984 }
985
986 /**
987 * Builds an array of the inital default protections.
988 *
989 * @return The default protections array.
990 */
991 function userprotect_user_protection_defaults() {
992 return array('up_name' => 0,
993 'up_mail' => 0,
994 'up_pass' => 0,
995 'up_status' => 1,
996 'up_roles' => 0,
997 'up_openid' => 0,
998 'up_cancel' => 1,
999 'up_edit' => 0,
1000 );
1001 }
1002
1003 /**
1004 * Builds an array of the inital default bypass settings for user admins.
1005 *
1006 * @return The default bypass array.
1007 */
1008 function userprotect_administrator_bypass_defaults() {
1009
1010 $defaults = array();
1011 $protections = userprotect_user_protection_defaults();
1012 foreach ($protections as $protection => $value) {
1013 $defaults[$protection] = $protection;
1014 }
1015
1016 return $defaults;
1017 }
1018
1019 /**
1020 * Builds an array of all protections and their human-readable text string.
1021 *
1022 * @return The constructed array.
1023 */
1024 function userprotect_get_protection_display() {
1025 return array('up_name' => t('username'),
1026 'up_mail' => t('e-mail'),
1027 'up_pass' => t('password'),
1028 'up_status' => t('status'),
1029 'up_roles' => t('roles'),
1030 'up_openid' => t('openid'),
1031 'up_cancel' => t('cancel'),
1032 'up_edit' => t('all account edits'),
1033 );
1034 }
1035
1036 /**
1037 * Conditionally displays a user message on edit forms listing current
1038 * protections.
1039 *
1040 * @param $account The user account object.
1041 * @param $protected An array of protections the current user is receiving.
1042 */
1043 function userprotect_form_display_protections($account, $protected) {
1044 // If we're initially displaying an edit form, throw a message if
1045 // there are any protected fields, so the editor has a clue.
1046 if (!empty($protected) && !$_POST) {
1047 drupal_set_message(userprotect_display_protections($account, $protected));
1048 }
1049 }
1050
1051 /**
1052 * Builds a displayable text string of the protections currently in effect for
1053 * the specified user.
1054 *
1055 * @param $account The user account object.
1056 * @param $protected An array of protections the current user is receiving.
1057 *
1058 * @return A text string representing the current protections.
1059 */
1060 function userprotect_display_protections($account, $protected) {
1061
1062 // Get the protections display text.
1063 $display = userprotect_get_protection_display();
1064
1065 $protections = array();
1066 // For each protection, check if any of the user's roles are protected, or the user is
1067 // protected.
1068 foreach ($protected as $protection => $value) {
1069 $protections[] = $display[$protection];
1070 }
1071 // Display if there are protections and it's an admin user.
1072 if (count($protections) && user_access('administer users')) {
1073 $output = t('%user has been protected from the following editing operations: !operations', array('%user' => $account->name, '!operations' => implode(', ', $protections)));
1074 }
1075 else {
1076 $output = '';
1077 }
1078
1079 return $output;
1080 }
1081
1082 /**
1083 * Adds a user to the protections table.
1084 *
1085 * @param $uid The UID of the user to be added.
1086 * @param $type The type of protection to add, either 'user', or 'admin'.
1087 */
1088 function userprotect_add_user($uid, $type) {
1089 // Grab the default protections to enable for this user.
1090 $protections = variable_get('userprotect_protection_defaults', userprotect_user_protection_defaults());
1091
1092 // Set initial fields.
1093 $fields = array(
1094 'uid' => $uid,
1095 'up_type' => $type,
1096 );
1097
1098 // Add the protections.
1099 foreach ($protections as $protection => $value) {
1100 $fields[$protection] = $protections[$protection] ? 1 : 0;
1101 }
1102
1103 db_insert('userprotect')
1104 ->fields($fields)
1105 ->execute();
1106 }
1107
1108 /**
1109 * Gives the username of a protected user.
1110 *
1111 * @param $uid The user ID.
1112 * @return The username.
1113 */
1114 function userprotect_get_username($uid) {
1115 return db_query('SELECT name FROM {users} WHERE uid = :uid', array(
1116 ':uid' => $uid,
1117 ))->fetchField();
1118 }
1119
1120 /**
1121 * Checks to see if the current user can bypass a protection.
1122 *
1123 * @param $protection The protection to check for bypass.
1124 * @param $uid An optional user to perform the bypass check on (default is current user).
1125 *
1126 * @return TRUE if the user can bypass, FALSE otherwise.
1127 */
1128 function userprotect_check_bypass($protection, $uid = NULL) {
1129
1130 $bypass = &drupal_static(__FUNCTION__, array());
1131 $bypass_defaults = &drupal_static(__FUNCTION__ . '_defaults', NULL);
1132
1133 // If not a user admin, no checks necessary.
1134 if (!user_access('administer users')) {
1135 return FALSE;
1136 }
1137
1138 // Take the current user unless otherwise specified.
1139 $uid = isset($uid) ? $uid : $GLOBALS['user']->uid;
1140
1141 // Set the static array for the current admin.
1142 if (!isset($bypass[$uid])) {
1143 $result = db_query("SELECT * FROM {userprotect} WHERE uid = :uid AND up_type = :up_type", array(
1144 ':uid' => $uid,
1145 ':up_type' => 'admin',
1146 ));
1147 if ($admin_array = $result->fetchAssoc()) {
1148 $bypass[$uid] = $admin_array;
1149 }
1150 }
1151
1152 // If a per administrator bypass setting exists, return it.
1153 if (isset($bypass[$uid][$protection])) {
1154 return $bypass[$uid][$protection];
1155 }
1156 // Otherwise return the default bypass setting.
1157 else {
1158 if (!isset($bypass_defaults)) {
1159 $bypass_defaults = variable_get('userprotect_administrator_bypass_defaults', userprotect_administrator_bypass_defaults());
1160 }
1161
1162 return isset($bypass_defaults[$protection]) ? $bypass_defaults[$protection] : FALSE;
1163 }
1164
1165 }
1166
1167 /**
1168 * Checks to see if the specified user has the specified protection.
1169 *
1170 * @param $account The user object to check.
1171 * @param $protection The protection to check for.
1172 * @return TRUE if the user has the specified protection, FALSE otherwise.
1173 */
1174 function userprotect_get_user_protection($account, $protection) {
1175
1176 $protections = &drupal_static(__FUNCTION__, array());
1177 $role_protections = &drupal_static(__FUNCTION__ . '_roles', NULL);
1178
1179 $uid = $account->uid;
1180 $roles = $account->roles;
1181
1182 // Users editing their own accounts have the permissions for e-mail
1183 // and password determined by the role-based setting in the userprotect
1184 // section at admin/config/people/permissions. This is done for consistency
1185 // with the way core handles the self-editing of usernames.
1186 if ($uid == $GLOBALS['user']->uid && in_array($protection, array('up_name', 'up_mail', 'up_pass', 'up_openid', 'up_edit'))) {
1187 switch ($protection) {
1188 case 'up_name':
1189 return !user_access('change own username');
1190 case 'up_mail':
1191 return !user_access('change own e-mail');
1192 case 'up_pass':
1193 return !user_access('change own password');
1194 case 'up_openid':
1195 return !user_access('change own openid');
1196 // Always let user access their own edit page.
1197 case 'up_edit':
1198 return FALSE;
1199 }
1200 }
1201
1202 // If this user hasn't been added to the result array yet, then pull their information.
1203 if (!isset($protections[$uid])) {
1204
1205 $result = db_query("SELECT * FROM {userprotect} WHERE uid = :uid AND up_type = :up_type", array(
1206 ':uid' => $uid,
1207 ':up_type' => 'user',
1208 ));
1209 if ($user_array = $result->fetchAssoc()) {
1210 $protections[$uid] = $user_array;
1211 }
1212 }
1213
1214 // If per-user protections exist for this user, stop here and return the value of the protection.
1215 if (isset($protections[$uid][$protection])) {
1216 return $protections[$uid][$protection];
1217 }
1218
1219 // Grab the role protections if they haven't already been initialized.
1220 if (!isset($role_protections)) {