# Skill: Users & Permissions

## Identity
- **Skill ID**: `users-permissions`
- **Domain**: WordPress Roles, Capabilities & Access Control
- **Technologies**: WordPress Roles/Capabilities API, REST API Permissions
- **Source Agent**: `users-permissions-expert.md`

## When to Load This Skill
- Task involves permission checks
- Implementing role-based access control
- Creating custom capabilities
- Working with REST API permission callbacks
- Files matching: `includes/**/Permissions.php`, `includes/Api/Rest*.php`

## Core Patterns

### Capability Checks (MANDATORY)
```php
// CORRECT - Always use current_user_can()
if (!current_user_can('manage_options')) {
    wp_die(__('You do not have permission to access this page.', 'international-press-zone'));
}

// CORRECT - Check specific capability for action
if (!current_user_can('edit_post', $post_id)) {
    wp_send_json_error(['message' => __('Cannot edit this post.', 'international-press-zone')]);
}

// WRONG - is_admin() is NOT a security check
if (is_admin()) {  // This only checks if in admin area, not permissions!
    // DANGER: Any logged-in user in admin area passes this
}
```

### REST API Permission Callbacks
```php
register_rest_route('international-press-zone/v1', '/languages', [
    'methods' => 'GET',
    'callback' => [$this, 'get_languages'],
    'permission_callback' => function() {
        return current_user_can('manage_options');
    }
]);

register_rest_route('international-press-zone/v1', '/translations/(?P<id>\d+)', [
    'methods' => 'PUT',
    'callback' => [$this, 'update_translation'],
    'permission_callback' => function($request) {
        $post_id = $request->get_param('id');
        return current_user_can('edit_post', $post_id);
    },
    'args' => [
        'id' => [
            'required' => true,
            'validate_callback' => function($param) {
                return is_numeric($param) && $param > 0;
            },
            'sanitize_callback' => 'absint'
        ]
    ]
]);
```

### AJAX Permission Checks
```php
function presszone_international_ajax_handler() {
    // 1. Verify nonce first
    $nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
    if (!wp_verify_nonce($nonce, 'presszone_international_nonce')) {
        wp_send_json_error(['message' => __('Security check failed.', 'international-press-zone')]);
    }

    // 2. Check capability
    if (!current_user_can('manage_options')) {
        wp_send_json_error(['message' => __('Insufficient permissions.', 'international-press-zone')]);
    }

    // 3. Process action...
}
```

### Custom Capabilities Registration
```php
function presszone_international_register_capabilities() {
    $admin = get_role('administrator');

    if ($admin) {
        $admin->add_cap('presszone_international_manage_languages');
        $admin->add_cap('presszone_international_edit_translations');
        $admin->add_cap('presszone_international_view_analytics');
    }

    // Add to editor role (limited)
    $editor = get_role('editor');
    if ($editor) {
        $editor->add_cap('presszone_international_edit_translations');
    }
}
register_activation_hook(__FILE__, 'presszone_international_register_capabilities');

// Remove on deactivation
function presszone_international_remove_capabilities() {
    $roles = ['administrator', 'editor'];
    $caps = [
        'presszone_international_manage_languages',
        'presszone_international_edit_translations',
        'presszone_international_view_analytics'
    ];

    foreach ($roles as $role_name) {
        $role = get_role($role_name);
        if ($role) {
            foreach ($caps as $cap) {
                $role->remove_cap($cap);
            }
        }
    }
}
register_deactivation_hook(__FILE__, 'presszone_international_remove_capabilities');
```

### Multi-Level Permission Check
```php
function presszone_international_can_manage() {
    return current_user_can('manage_options') ||
           current_user_can('presszone_international_manage_languages');
}

function presszone_international_can_edit_translation($post_id) {
    // Admins can edit all
    if (current_user_can('manage_options')) {
        return true;
    }

    // Check custom capability + post ownership
    if (current_user_can('presszone_international_edit_translations')) {
        $post = get_post($post_id);
        return $post && $post->post_author == get_current_user_id();
    }

    return false;
}
```

## Anti-Patterns (Forbidden)

| Mistake | Fix |
|---------|-----|
| Using `is_admin()` for security | Use `current_user_can()` |
| Checking role slugs | Check capabilities, not roles |
| Missing login check | Use `is_user_logged_in()` when needed |
| Hardcoded user IDs | Use capabilities or meta checks |
| No permission_callback in REST | Always specify, never use `__return_true` for protected routes |
| Checking after action | Check permissions BEFORE performing action |

## WordPress.org Compliance

### Core Capabilities Reference

| Capability | Who Has It | Use For |
|------------|-----------|---------|
| `manage_options` | Admin | Site-wide settings |
| `edit_posts` | Author+ | Creating content |
| `edit_others_posts` | Editor+ | Editing others' content |
| `publish_posts` | Author+ | Publishing content |
| `delete_posts` | Author+ | Deleting own content |
| `moderate_comments` | Editor+ | Comment moderation |
| `upload_files` | Author+ | Media uploads |

### Capability vs Role
```php
// CORRECT - Check capability
if (current_user_can('edit_posts')) { }

// WRONG - Check role (fragile, roles can be customized)
$user = wp_get_current_user();
if (in_array('editor', $user->roles)) { }
```

## Integration with Other Skills
- **Often combined with**: `wordpress-php-integration`, `api-integration`
- **For admin pages**: Load `admin-panel-fullstack`
- **For database access**: Load `database-operations` with user_id checks

## Quick Reference

### Common Permission Functions
```php
current_user_can($capability);          // Check current user
current_user_can($capability, $object_id); // Check against specific object
user_can($user, $capability);           // Check specific user
is_user_logged_in();                    // Check if logged in
get_current_user_id();                  // Get current user ID
```

### User Meta for Moderation
```php
// Mute a user
update_user_meta($user_id, 'presszone_international_muted_until', time() + 86400);

// Check if muted
$muted_until = get_user_meta($user_id, 'presszone_international_muted_until', true);
if ($muted_until && $muted_until > time()) {
    // User is muted
}

// Ban a user
update_user_meta($user_id, 'presszone_international_banned', 1);

// Protect admins from bans
if (user_can($user_id, 'administrator')) {
    // Cannot ban administrators
    return new WP_Error('cannot_ban_admin', __('Cannot ban administrators.', 'international-press-zone'));
}
```

## Validation Checklist
- [ ] Using `current_user_can()` for all permission checks
- [ ] NOT using `is_admin()` for security
- [ ] Checking capabilities, not role slugs
- [ ] REST routes have proper `permission_callback`
- [ ] Nonce verified before capability check
- [ ] Administrators protected from moderation actions
- [ ] Custom capabilities registered on activation
- [ ] Custom capabilities removed on deactivation
