Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

ModuleUsersGroups — модуль расширения для MikoPBX, реализующий управление группами сотрудников с контролем прав на звонки, изоляцией вызовов и управлением исходящими маршрутами. Модуль генерирует конфигурацию Asterisk dialplan и управляет данными через AstDB.

## Architecture

### Framework & Runtime
- **PHP 7.4+** с Phalcon (поддержка v4 и v5+ через `Lib/MikoPBXVersion.php`)
- Наследует базовые классы MikoPBX: `ConfigClass`, `PbxExtensionBase`, `ModulesModelsBase`, `BaseController`, `BaseForm`
- Namespace: `Modules\ModuleUsersGroups\` (PSR-4 от корня)
- Frontend: Semantic UI + DataTables, JS на ES6+

### Core Components

**`Lib/UsersGroupsConf.php`** — центральный класс, наследует `ConfigClass`. Реализует хуки MikoPBX для генерации dialplan:
- `extensionGenAllPeersContext()` — правила изоляции в контексте `[all_peers]`
- `extensionGenContexts()` — контексты `users-group-isolate`, `users-group-dst-*`, `users-group-forbidden`
- `generateOutRoutContext()` / `generateOutRoutAfterDialContext()` — ограничения исходящих маршрутов, подмена CallerID
- `overridePJSIPOptions()` — назначение `named_call_group`/`named_pickup_group` для изоляции pickup
- `moduleRestAPICallback()` — точка входа REST API
- `onAfterExecuteRestAPIRoute()` — перехват сохранения сотрудника (API v2 и v3) для привязки к группе

**`Lib/UsersGroups.php`** — сервисный класс, заполняет AstDB (`UsersGroups/{extension}`) переменными канала: `GR_PERM_ENABLE`, `GR_ID_{routeId}`, `GR_CID_{routeId}`.

**`Lib/RestAPI/`** — action-based REST API:
- `UsersGroupsManagementProcessor.php` — роутер действий
- Actions: `GetUserGroupAction`, `UpdateUserGroupAction`, `SetDefaultGroupAction`, `GetDefaultGroupAction`, `GetGroupsStatsAction`, `CleanupOrphanedMembersAction`

### Data Model (SQLite, ORM через аннотации Phalcon)
- `UsersGroups` — группа (name, description, patterns, isolate, isolatePickUp, defaultGroup)
- `GroupMembers` — связь группа↔пользователь (group_id, user_id). Один пользователь = одна группа
- `AllowedOutboundRules` — связь группа↔маршрут (group_id, rule_id, caller_id)

**Важно:** `group_id` в коде используется со сдвигом `+1` при маппинге на named_call_group Asterisk (см. `initUserList()` и `getSettings()`).

### Dialplan Logic
Изоляция работает через контексты Asterisk:
1. В `[all_peers]` проверяются флаги `srcIsolate` и `dstIsolate` через `DIALPLAN_EXISTS`
2. При запрете — `Goto(users-group-forbidden)` с воспроизведением звукового файла
3. Исходящие маршруты: переменные из AstDB (`GR_VARS`) определяют доступ к маршруту и подмену CallerID
4. Поддержка `FW_SOURCE_PEER` для переадресованных вызовов

### Frontend Structure
- `App/Controllers/ModuleUsersGroupsController.php` — единый контроллер (index/modify)
- `App/Views/ModuleUsersGroups/` — Volt-шаблоны с табами (groups, users, rules)
- `public/assets/js/src/` — исходники JS, `public/assets/js/` — собранные файлы

## Build & CI

Сборка и публикация через GitHub Actions (`.github/workflows/build.yml`), использует shared workflow `mikopbx/.github-workflows/.github/workflows/extension-publish.yml@master`.

Зависимости PHP: `composer install` (минимальные — только `mikopbx/core`).

Тесты и линтеры не настроены в репозитории.

## Localization

32 языковых файла в `Messages/`. Звуковые файлы `Sounds/{lang}/forbidden.mp3` для голосовых уведомлений при запрете вызова.

## Key Conventions

- PHP 7.4 совместимость обязательна (нет `str_starts_with`, `match`, union types и т.д.)
- Модуль-специфичные POST-поля имеют префикс `mod_usrgr_`
- Модуль встраивается в карточку сотрудника через `onVoltBlockCompile` и `onBeforeFormInitialize`
- При изменении моделей `AllowedOutboundRules`, `GroupMembers`, `UsersGroups` автоматически вызывается `reloadConfigs()` (SIP + dialplan reload)
6 changes: 3 additions & 3 deletions Lib/UsersGroups.php
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public function fillAsteriskDatabase(): void
$db = new AstDB();
$extension = Extensions::find("type='SIP'")->toArray();
if ($enabled === false) {
$cmd = "ARRAY(GR_PERM_ENABLE)=0)";
$cmd = "ARRAY(GR_PERM_ENABLE)=0";

// Loop through each extension and disable group permissions
foreach ($extension as $extensionData) {
Expand Down Expand Up @@ -137,7 +137,7 @@ private function initGroupID(array $extensionData, array $groupMembers): array
$groupId = null;
$number = $extensionData['number'];
foreach ($groupMembers as $memberData) {
if ($memberData['user_id'] === $extensionData['userid']) {
if (intval($memberData['user_id']) === intval($extensionData['userid'])) {
$groupId = $memberData['group_id'];
break;
}
Expand All @@ -160,7 +160,7 @@ private function initChannelVariables($group_id, array $allowedRules): string

// Find all routes allowed in the group
foreach ($allowedRules as $ruleData) {
if ($ruleData['group_id'] !== $group_id) {
if (intval($ruleData['group_id']) !== intval($group_id)) {
continue;
}

Expand Down
2 changes: 1 addition & 1 deletion Lib/UsersGroupsConf.php
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ public function generateOutRoutContext(array $rout): string
$conf .= 'same => n,Set(EFFECTIVE_FROM_PEER=${IF($["${FW_SOURCE_PEER}x" != "x"]?${FW_SOURCE_PEER}:${FROM_PEER})})' . " \n\t";
$conf .= 'same => n,Set(GR_VARS=${DB(UsersGroups/${EFFECTIVE_FROM_PEER})})' . " \n\t";
$conf .= 'same => n,ExecIf($["${GR_VARS}x" != "x"]?Exec(Set(${GR_VARS})))' . " \n\t";
$conf .= 'same => n,ExecIf($["${GR_PERM_ENABLE}" == "1" && "${GR_ID_' . $rout['id'] . '}" != "1"]?Goto(users-group-forbidden,${EXTEN},1))' . " \n\t";
$conf .= 'same => n,ExecIf($["${GR_PERM_ENABLE}" == "1" && "${GR_ID_' . $rout['id'] . '}" != "1"]?return)' . " \n\t";
$conf .= 'same => n,ExecIf($["${GR_PERM_ENABLE}" == "1" && "${GR_CID_' . $rout['id'] . '}x" != "x"]?MSet(GR_OLD_CALLERID=${CALLERID(num)},OUTGOING_CID=${GR_CID_' . $rout['id'] . '}))' . "\n\t";
$conf .= 'same => n,ExecIf($["${OUTGOING_CID}x" != "x"]?Set(DOPTIONS=${DOPTIONS}f(${OUTGOING_CID})))' . " \n\t";
$conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(SIP-${CUT(CONTEXT,-,2)}-outgoing-ug-custom,${EXTEN},1)}" == "1"]?SIP-${CUT(CONTEXT,-,2)}-outgoing-ug-custom,${EXTEN},1)';
Expand Down