---
name: perfex-permissions
description: Sistema de permissões Perfex CRM — staff_can(), register_staff_capabilities(), controlo de acesso e roles. Baseado apenas na documentação oficial.
---

# /perfex-permissions - Permissões Perfex CRM

Sistema de permissões e controlo de acesso. **Zero assumptions, zero hallucinations** - apenas documentação oficial.

---

## Documentação Base

- [Staff Capabilities and Access](https://help.perfexcrm.com/staff-capabilities-and-access/)
- [Roles](https://help.perfexcrm.com/roles/)

---

## Conceitos Fundamentais

| Conceito | Descrição |
|----------|-----------|
| **Feature** | Área funcional (invoices, projects, meu_modulo) |
| **Capability** | Permissão específica (view, create, edit, delete) |
| **Role** | Conjunto de permissões pré-definido |

---

## Registar Permissões do Módulo

### No Init File

```php
hooks()->add_action('admin_init', 'meu_modulo_register_permissions');

function meu_modulo_register_permissions()
{
    $capabilities = [];

    $capabilities['capabilities'] = [
        'view'   => _l('permission_view'),    // Ver
        'create' => _l('permission_create'),  // Criar
        'edit'   => _l('permission_edit'),    // Editar
        'delete' => _l('permission_delete'),  // Apagar
    ];

    register_staff_capabilities(
        'meu_modulo',           // Feature ID (único)
        $capabilities,          // Array de capabilities
        _l('meu_modulo_title')  // Nome do módulo (mostrado em Settings)
    );
}
```

### Parâmetros register_staff_capabilities()

| Parâmetro | Tipo | Descrição |
|-----------|------|-----------|
| `$feature` | string | ID único da feature |
| `$capabilities` | array | Array com key 'capabilities' |
| `$name` | string | Nome mostrado na UI |

---

## Verificar Permissões: staff_can()

### Sintaxe

```php
staff_can($capability, $feature = null, $staff_id = '');
```

### Parâmetros

| Parâmetro | Tipo | Descrição |
|-----------|------|-----------|
| `$capability` | string | Nome da capability (view, create, etc.) |
| `$feature` | string | Feature ID (recomendado SEMPRE passar) |
| `$staff_id` | int | ID do staff (default: staff logado) |

### Retorno

- `true` - Staff tem permissão
- `false` - Staff não tem permissão

**NOTA:** Administradores SEMPRE retornam `true` (bypass total).

---

## Exemplos de Uso

### No Controller

```php
class Meu_modulo extends AdminController
{
    public function index()
    {
        // Verificar permissão de ver
        if (!staff_can('view', 'meu_modulo')) {
            access_denied('meu_modulo');
        }

        // ... código
    }

    public function create()
    {
        // Verificar permissão de criar
        if (!staff_can('create', 'meu_modulo')) {
            access_denied('meu_modulo');
        }

        // ... código
    }

    public function edit($id)
    {
        // Verificar permissão de editar
        if (!staff_can('edit', 'meu_modulo')) {
            access_denied('meu_modulo');
        }

        // ... código
    }

    public function delete($id)
    {
        // Verificar permissão de apagar
        if (!staff_can('delete', 'meu_modulo')) {
            access_denied('meu_modulo');
        }

        // ... código
    }
}
```

### Na View

```php
<!-- Mostrar botão apenas se tem permissão -->
<?php if (staff_can('create', 'meu_modulo')): ?>
    <a href="<?php echo admin_url('meu_modulo/create'); ?>" class="btn btn-primary">
        <i class="fa fa-plus"></i> <?php echo _l('create'); ?>
    </a>
<?php endif; ?>

<!-- Botões de acção condicionais -->
<td>
    <?php if (staff_can('edit', 'meu_modulo')): ?>
        <a href="<?php echo admin_url('meu_modulo/edit/' . $item->id); ?>"
           class="btn btn-default btn-icon">
            <i class="fa fa-pencil"></i>
        </a>
    <?php endif; ?>

    <?php if (staff_can('delete', 'meu_modulo')): ?>
        <a href="<?php echo admin_url('meu_modulo/delete/' . $item->id); ?>"
           class="btn btn-danger btn-icon _delete">
            <i class="fa fa-trash"></i>
        </a>
    <?php endif; ?>
</td>
```

### No Menu

```php
function meu_modulo_init_menu()
{
    // Só mostrar menu se tem permissão
    if (!staff_can('view', 'meu_modulo')) {
        return;
    }

    $CI = &get_instance();
    $CI->app_menu->add_sidebar_menu_item('meu-modulo', [
        'name'     => _l('meu_modulo'),
        'href'     => admin_url('meu_modulo'),
        'position' => 25,
        'icon'     => 'fa fa-cube',
    ]);
}
```

---

## Capabilities Padrão

### Recomendadas para CRUD

| Capability | Descrição | Uso |
|------------|-----------|-----|
| `view` | Ver registos | Listar, ver detalhes |
| `create` | Criar novos | Formulário de criação |
| `edit` | Editar existentes | Formulário de edição |
| `delete` | Apagar registos | Botão de eliminar |

### Adicionar Capabilities Custom

```php
$capabilities['capabilities'] = [
    // CRUD básico
    'view'   => _l('permission_view'),
    'create' => _l('permission_create'),
    'edit'   => _l('permission_edit'),
    'delete' => _l('permission_delete'),

    // Custom
    'export'     => _l('permission_export'),      // Exportar dados
    'import'     => _l('permission_import'),      // Importar dados
    'send_email' => _l('permission_send_email'),  // Enviar emails
    'view_all'   => _l('permission_view_all'),    // Ver todos (não só próprios)
    'approve'    => _l('permission_approve'),     // Aprovar items
];
```

---

## Adicionar Permissões a Features Existentes

Para adicionar capabilities a módulos do core (invoices, projects, etc.):

```php
hooks()->add_action('admin_init', 'meu_modulo_extend_permissions');

function meu_modulo_extend_permissions()
{
    $capabilities = [];

    $capabilities['capabilities'] = [
        'meu_modulo_custom_action' => _l('meu_modulo_custom_action_label'),
    ];

    // Adicionar à feature "invoices"
    register_staff_capabilities('invoices', $capabilities);
}
```

**Uso:**

```php
if (staff_can('meu_modulo_custom_action', 'invoices')) {
    // Acção custom em facturas
}
```

---

## Verificar Administrador

```php
// Verificar se é admin (tem todas as permissões)
if (is_admin()) {
    // Utilizador é administrador
}

// Verificar se é admin específico (ID 1)
if (is_admin(1)) {
    // Staff com ID 1 é admin
}
```

---

## Funções Auxiliares

### Acesso Negado

```php
// Redireciona com mensagem de acesso negado
access_denied('meu_modulo');
```

### Verificar Staff Logado

```php
// ID do staff actual
$staff_id = get_staff_user_id();

// Verificar se está logado
if (is_staff_logged_in()) {
    // Staff autenticado
}
```

### Verificar Permissão de Outro Staff

```php
// Verificar se staff ID 5 pode editar
$can_edit = staff_can('edit', 'meu_modulo', 5);
```

---

## Exemplo Completo

### Init File

```php
<?php
defined('BASEPATH') or exit('No direct script access allowed');

/*
Module Name: Gestão de Inventário
Description: Sistema de inventário com controlo de acesso
Version: 1.0.0
Requires at least: 2.3.*
*/

// Registar permissões
hooks()->add_action('admin_init', 'inventario_register_permissions');
hooks()->add_action('admin_init', 'inventario_init_menu');

function inventario_register_permissions()
{
    $capabilities = [
        'capabilities' => [
            'view'       => _l('permission_view'),
            'create'     => _l('permission_create'),
            'edit'       => _l('permission_edit'),
            'delete'     => _l('permission_delete'),
            'export'     => _l('inventory_permission_export'),
            'adjust'     => _l('inventory_permission_adjust'),  // Ajustar stock
            'view_costs' => _l('inventory_permission_view_costs'),  // Ver custos
        ],
    ];

    register_staff_capabilities(
        'inventario',
        $capabilities,
        _l('inventory_module')
    );
}

function inventario_init_menu()
{
    if (!staff_can('view', 'inventario')) {
        return;
    }

    $CI = &get_instance();

    $CI->app_menu->add_sidebar_menu_item('inventario', [
        'name'     => _l('inventory'),
        'collapse' => true,
        'position' => 22,
        'icon'     => 'fa fa-cubes',
    ]);

    $CI->app_menu->add_sidebar_children_item('inventario', [
        'slug'     => 'inventario-lista',
        'name'     => _l('products'),
        'href'     => admin_url('inventario'),
        'position' => 1,
        'icon'     => 'fa fa-list',
    ]);

    if (staff_can('adjust', 'inventario')) {
        $CI->app_menu->add_sidebar_children_item('inventario', [
            'slug'     => 'inventario-ajustes',
            'name'     => _l('adjustments'),
            'href'     => admin_url('inventario/adjustments'),
            'position' => 2,
            'icon'     => 'fa fa-exchange',
        ]);
    }

    if (staff_can('export', 'inventario')) {
        $CI->app_menu->add_sidebar_children_item('inventario', [
            'slug'     => 'inventario-export',
            'name'     => _l('export'),
            'href'     => admin_url('inventario/export'),
            'position' => 3,
            'icon'     => 'fa fa-download',
        ]);
    }
}
```

### Controller

```php
<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Inventario extends AdminController
{
    public function __construct()
    {
        parent::__construct();
        $this->load->model('inventario/inventario_model');
    }

    public function index()
    {
        if (!staff_can('view', 'inventario')) {
            access_denied('inventario');
        }

        $data['products'] = $this->inventario_model->get_all();

        // Se pode ver custos, incluir
        if (staff_can('view_costs', 'inventario')) {
            $data['show_costs'] = true;
        }

        $this->load->view('inventario/index', $data);
    }

    public function adjustments()
    {
        if (!staff_can('adjust', 'inventario')) {
            access_denied('inventario');
        }

        // ... lógica de ajustes
    }

    public function export()
    {
        if (!staff_can('export', 'inventario')) {
            access_denied('inventario');
        }

        // ... lógica de exportação
    }
}
```

---

## Anti-Patterns (NUNCA FAZER)

| Anti-Pattern | Risco | Alternativa |
|--------------|-------|-------------|
| Não verificar permissões no controller | Acesso não autorizado | `staff_can()` sempre |
| Confiar apenas na UI | Bypass via URL | Verificar em backend |
| Feature ID duplicado | Conflitos | Prefixar com nome módulo |
| Capability sem _l() | Não traduzível | Usar traduções |
| Assumir que view esconde acção | Vulnerável | Verificar em ambos |

---

## Checklist Permissões

```
1. [ ] register_staff_capabilities no init file
2. [ ] Feature ID único
3. [ ] Capabilities com traduções (_l)
4. [ ] staff_can() em TODOS os métodos do controller
5. [ ] staff_can() nas views para UI condicional
6. [ ] staff_can() antes de mostrar menus
7. [ ] access_denied() para rejeitar acesso
8. [ ] Testado com utilizador não-admin
```

---

**Versão:** 1.0.0 | **Autor:** Descomplicar®
**Fonte:** help.perfexcrm.com/staff-capabilities-and-access

---

## Healing Log

Registo de erros conhecidos e como evitá-los. Lido automaticamente antes de executar.

```jsonl
{"date":"","issue":"","fix":"","source":"user|auto"}
```

*Adicionar nova linha após cada erro corrigido.*
