refactor: align addressbook module to MintyPHP\Module\AddressBook namespace
Migrates addressbook service classes from core namespace (MintyPHP\Service\AddressBook\*) to proper module namespace (MintyPHP\Module\AddressBook\Service\*), consistent with the bookmarks module pattern. Moved to module: - AddressBookService → modules/addressbook/lib/Module/AddressBook/Service/ - AddressBookServicesFactory → modules/addressbook/lib/Module/AddressBook/Service/ - AddressBookServiceTest → modules/addressbook/tests/Module/AddressBook/Service/ - Pages, templates, CSS, JS all in module directory All module PHP classes now live under MintyPHP\Module\<Name>\*, enforced by ModuleStructureContractTest::testModuleClassesUseModuleNamespace. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook;
|
||||
|
||||
use MintyPHP\Service\Access\AuthorizationDecision;
|
||||
use MintyPHP\Service\Access\AuthorizationPolicyInterface;
|
||||
use MintyPHP\Service\Access\PermissionService;
|
||||
|
||||
/**
|
||||
* Authorization policy for address book abilities.
|
||||
*
|
||||
* Owns the 'addressbook.view' ability and delegates to the
|
||||
* 'address_book.view' permission in the Core PermissionService.
|
||||
*/
|
||||
final class AddressBookAuthorizationPolicy implements AuthorizationPolicyInterface
|
||||
{
|
||||
public const ABILITY_VIEW = 'addressbook.view';
|
||||
|
||||
public function __construct(
|
||||
private readonly PermissionService $permissionService
|
||||
) {
|
||||
}
|
||||
|
||||
public function supports(string $ability): bool
|
||||
{
|
||||
return $ability === self::ABILITY_VIEW;
|
||||
}
|
||||
|
||||
public function authorize(string $ability, array $context = []): AuthorizationDecision
|
||||
{
|
||||
$actorUserId = (int) ($context['actor_user_id'] ?? 0);
|
||||
if ($actorUserId <= 0) {
|
||||
return AuthorizationDecision::deny(403, 'forbidden');
|
||||
}
|
||||
|
||||
if (!$this->permissionService->userHas($actorUserId, PermissionService::ADDRESS_BOOK_VIEW)) {
|
||||
return AuthorizationDecision::deny(403, 'forbidden');
|
||||
}
|
||||
|
||||
return AuthorizationDecision::allow();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Container\ContainerRegistrar;
|
||||
use MintyPHP\Module\AddressBook\Service\AddressBookService;
|
||||
use MintyPHP\Module\AddressBook\Service\AddressBookServicesFactory;
|
||||
use MintyPHP\Service\CustomField\CustomFieldServicesFactory;
|
||||
use MintyPHP\Service\Directory\DirectoryServicesFactory;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
|
||||
final class AddressBookContainerRegistrar implements ContainerRegistrar
|
||||
{
|
||||
public function register(AppContainer $container): void
|
||||
{
|
||||
$container->set(AddressBookServicesFactory::class, static fn (AppContainer $c): AddressBookServicesFactory => new AddressBookServicesFactory(
|
||||
$c->get(UserServicesFactory::class),
|
||||
$c->get(DirectoryServicesFactory::class),
|
||||
$c->get(CustomFieldServicesFactory::class),
|
||||
$c->get(TenantScopeService::class)
|
||||
));
|
||||
|
||||
$container->set(AddressBookService::class, static fn (AppContainer $c): AddressBookService => $c
|
||||
->get(AddressBookServicesFactory::class)
|
||||
->createAddressBookService());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook\Providers;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\Contracts\LayoutContextProvider;
|
||||
|
||||
/**
|
||||
* Provides address book data for the layout navigation context.
|
||||
*
|
||||
* Contributes the 'addressbook.nav' key to $layoutNav with URL, active filters,
|
||||
* and people groups from the session.
|
||||
*/
|
||||
final class AddressBookLayoutProvider implements LayoutContextProvider
|
||||
{
|
||||
public function provide(array $session, AppContainer $container): array
|
||||
{
|
||||
// Read query params for active filter state
|
||||
$query = [];
|
||||
try {
|
||||
$query = requestInput()->queryAll();
|
||||
} catch (\Throwable) {
|
||||
// fail-open: may not be available in CLI context
|
||||
}
|
||||
|
||||
return [
|
||||
'addressbook.nav' => [
|
||||
'url' => lurl('address-book'),
|
||||
'activeSearch' => trim((string) ($query['search'] ?? '')),
|
||||
'activeTenants' => appNormalizeStringList($query['tenants'] ?? ''),
|
||||
'activeDepartments' => appNormalizePositiveIntList($query['departments'] ?? ''),
|
||||
'activeRoles' => appNormalizePositiveIntList($query['roles'] ?? ''),
|
||||
'activeCustomFilters' => self::normalizeCustomFilterQuery($query),
|
||||
'peopleGroups' => is_array($session['available_departments_by_tenant'] ?? null)
|
||||
? $session['available_departments_by_tenant']
|
||||
: [],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize address-book custom field query keys/values.
|
||||
*
|
||||
* @param array<string, mixed> $rawQuery
|
||||
* @return array<string, string|list<int>>
|
||||
*/
|
||||
public static function normalizeCustomFilterQuery(array $rawQuery): array
|
||||
{
|
||||
$normalized = [];
|
||||
foreach ($rawQuery as $rawKey => $rawValue) {
|
||||
$key = strtolower(trim((string) $rawKey));
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^cf_[a-f0-9-]{36}$/', $key)) {
|
||||
$value = trim((string) $rawValue);
|
||||
if ($value !== '') {
|
||||
$normalized[$key] = $value;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^cfm_[a-f0-9-]{36}$/', $key)) {
|
||||
$ids = appNormalizePositiveIntList($rawValue);
|
||||
if ($ids) {
|
||||
$normalized[$key] = $ids;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^cfd_[a-f0-9-]{36}_(from|to)$/', $key)) {
|
||||
$value = trim((string) $rawValue);
|
||||
$dt = \DateTimeImmutable::createFromFormat('Y-m-d', $value);
|
||||
if ($dt && $dt->format('Y-m-d') === $value) {
|
||||
$normalized[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
ksort($normalized, SORT_STRING);
|
||||
return $normalized;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook\Providers;
|
||||
|
||||
use MintyPHP\App\Module\Contracts\SearchResourceProvider;
|
||||
|
||||
/**
|
||||
* Provides the address-book search resource for the global search.
|
||||
*/
|
||||
final class AddressBookSearchProvider implements SearchResourceProvider
|
||||
{
|
||||
public function resources(): array
|
||||
{
|
||||
return [
|
||||
'address-book' => [
|
||||
'label' => t('Address book'),
|
||||
'permission' => 'address_book.view',
|
||||
'count_sql' => "select count(*) from users where active = 1 and (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}}",
|
||||
'preview_sql' => "select uuid, first_name, last_name, email from users where active = 1 and (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}} order by last_name, first_name limit ?",
|
||||
'result_sql' => "select uuid, first_name, last_name, email from users where active = 1 and (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}} order by last_name, first_name",
|
||||
'tenant_filter' => 'and exists (select 1 from user_tenants ut where ut.user_id = users.id and ut.tenant_id in (???))',
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function mapResultItem(string $resourceKey, array $row): ?array
|
||||
{
|
||||
if ($resourceKey !== 'address-book') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$title = trim(($row['first_name'] ?? '') . ' ' . ($row['last_name'] ?? ''));
|
||||
$subtitle = (string) ($row['email'] ?? '');
|
||||
$uuid = (string) ($row['uuid'] ?? '');
|
||||
|
||||
if ($title === '' && $subtitle === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'title' => $title !== '' ? $title : $subtitle,
|
||||
'subtitle' => $subtitle,
|
||||
'url' => lurl('address-book/view/' . $uuid),
|
||||
'icon' => 'bi-people',
|
||||
];
|
||||
}
|
||||
|
||||
public function listUrl(string $resourceKey, string $encodedSearch): string
|
||||
{
|
||||
if ($resourceKey !== 'address-book') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return lurl('address-book') . '?search=' . $encodedSearch;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook\Providers;
|
||||
|
||||
use MintyPHP\App\AppContainer;
|
||||
use MintyPHP\App\Module\Contracts\SessionProvider;
|
||||
use MintyPHP\Http\SessionStoreInterface;
|
||||
use MintyPHP\Service\User\UserTenantContextService;
|
||||
|
||||
/**
|
||||
* Populates session with department-by-tenant hierarchy data used by the
|
||||
* address book aside panel.
|
||||
*/
|
||||
final class AddressBookSessionProvider implements SessionProvider
|
||||
{
|
||||
public function populate(array $user, AppContainer $container): void
|
||||
{
|
||||
$userId = (int) ($user['id'] ?? 0);
|
||||
if ($userId <= 0) {
|
||||
unset($_SESSION['available_departments_by_tenant']);
|
||||
return;
|
||||
}
|
||||
|
||||
$tenantContext = $container->get(UserTenantContextService::class);
|
||||
$sessionStore = $container->get(SessionStoreInterface::class);
|
||||
if (!$tenantContext instanceof UserTenantContextService || !$sessionStore instanceof SessionStoreInterface) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sessionStore->set(
|
||||
'available_departments_by_tenant',
|
||||
$tenantContext->getAvailableDepartmentsByTenant($userId)
|
||||
);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
unset($_SESSION['available_departments_by_tenant']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook\Service;
|
||||
|
||||
use MintyPHP\Repository\CustomField\TenantCustomFieldOptionRepository;
|
||||
use MintyPHP\Service\Access\RoleService;
|
||||
use MintyPHP\Service\CustomField\UserCustomFieldValueService;
|
||||
use MintyPHP\Service\Org\DepartmentService;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\Tenant\TenantService;
|
||||
use MintyPHP\Service\User\UserAccountService;
|
||||
use MintyPHP\Service\User\UserAssignmentService;
|
||||
use MintyPHP\Service\User\UserAvatarService;
|
||||
|
||||
class AddressBookService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly UserAccountService $userAccountService,
|
||||
private readonly UserAssignmentService $userAssignmentService,
|
||||
private readonly TenantService $tenantService,
|
||||
private readonly DepartmentService $departmentService,
|
||||
private readonly RoleService $roleService,
|
||||
private readonly TenantScopeService $scopeGateway,
|
||||
private readonly UserCustomFieldValueService $customFieldValueService,
|
||||
private readonly UserAvatarService $avatarService
|
||||
) {
|
||||
}
|
||||
|
||||
public function buildIndexContext(int $currentUserId, array $query): array
|
||||
{
|
||||
$activeTenants = $this->splitCommaValues($query['tenants'] ?? '');
|
||||
$activeRoles = $this->splitCommaValues($query['roles'] ?? '');
|
||||
$activeDepartments = $this->splitCommaValues($query['departments'] ?? '');
|
||||
|
||||
$tenantIds = $this->normalizeIds($this->scopeGateway->getUserTenantIds($currentUserId));
|
||||
$tenants = $this->tenantService->list();
|
||||
if ($tenantIds) {
|
||||
$allowedTenantMap = array_fill_keys($tenantIds, true);
|
||||
$tenants = array_values(array_filter(
|
||||
$tenants,
|
||||
static function (array $tenant) use ($allowedTenantMap): bool {
|
||||
$tenantId = (int) ($tenant['id'] ?? 0);
|
||||
return $tenantId > 0 && isset($allowedTenantMap[$tenantId]);
|
||||
}
|
||||
));
|
||||
} elseif ($this->scopeGateway->isStrict()) {
|
||||
$tenants = [];
|
||||
}
|
||||
|
||||
$tenantItems = array_map(
|
||||
static fn (array $tenant): array => [
|
||||
'id' => (string) ($tenant['uuid'] ?? ''),
|
||||
'description' => (string) ($tenant['description'] ?? ''),
|
||||
],
|
||||
$tenants
|
||||
);
|
||||
|
||||
$departments = $tenantIds
|
||||
? $this->departmentService->listByTenantIds($tenantIds)
|
||||
: ($this->scopeGateway->isStrict() ? [] : $this->departmentService->list());
|
||||
|
||||
$roles = $this->roleService->listActive();
|
||||
|
||||
$customFieldFilterSpec = $this->customFieldValueService->extractCustomFieldFilterSpec($query, $currentUserId);
|
||||
$customFieldFilterDefinitions = $this->normalizeDefinitions(
|
||||
$customFieldFilterSpec['definitions']
|
||||
);
|
||||
$customFieldFilterQuery = $customFieldFilterSpec['query'];
|
||||
|
||||
$customFieldDefinitionIds = [];
|
||||
foreach ($customFieldFilterDefinitions as $definition) {
|
||||
$definitionId = (int) ($definition['id'] ?? 0);
|
||||
if ($definitionId > 0) {
|
||||
$customFieldDefinitionIds[] = $definitionId;
|
||||
}
|
||||
}
|
||||
$customFieldDefinitionIds = array_values(array_unique($customFieldDefinitionIds));
|
||||
|
||||
$customFieldOptions = TenantCustomFieldOptionRepository::listByDefinitionIds(
|
||||
$customFieldDefinitionIds,
|
||||
true
|
||||
);
|
||||
|
||||
$customFieldOptionsByDefinition = [];
|
||||
foreach ($customFieldOptions as $option) {
|
||||
$definitionId = (int) ($option['definition_id'] ?? 0);
|
||||
if ($definitionId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$customFieldOptionsByDefinition[$definitionId] ??= [];
|
||||
$customFieldOptionsByDefinition[$definitionId][] = [
|
||||
'id' => (string) ((int) ($option['id'] ?? 0)),
|
||||
'description' => (string) ($option['label'] ?? ''),
|
||||
];
|
||||
}
|
||||
|
||||
$tenantLabelById = [];
|
||||
foreach ($tenants as $tenant) {
|
||||
$tenantId = (int) ($tenant['id'] ?? 0);
|
||||
if ($tenantId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$tenantLabelById[$tenantId] = (string) ($tenant['description'] ?? '');
|
||||
}
|
||||
|
||||
foreach ($customFieldFilterDefinitions as &$definition) {
|
||||
$definitionId = (int) ($definition['id'] ?? 0);
|
||||
$tenantId = (int) ($definition['tenant_id'] ?? 0);
|
||||
$definition['options'] = $customFieldOptionsByDefinition[$definitionId] ?? [];
|
||||
$definition['tenant_label'] = $tenantLabelById[$tenantId] ?? '';
|
||||
}
|
||||
unset($definition);
|
||||
|
||||
$tenantDepartmentMap = [];
|
||||
$departmentMapByTenantId = [];
|
||||
foreach ($departments as $department) {
|
||||
$departmentId = (int) ($department['id'] ?? 0);
|
||||
$departmentTenantId = (int) ($department['tenant_id'] ?? 0);
|
||||
if ($departmentId <= 0 || $departmentTenantId <= 0) {
|
||||
continue;
|
||||
}
|
||||
$departmentMapByTenantId[$departmentTenantId] ??= [];
|
||||
$departmentMapByTenantId[$departmentTenantId][] = $departmentId;
|
||||
}
|
||||
|
||||
foreach ($tenants as $tenant) {
|
||||
$tenantId = (int) ($tenant['id'] ?? 0);
|
||||
$tenantUuid = (string) ($tenant['uuid'] ?? '');
|
||||
if ($tenantId <= 0 || $tenantUuid === '') {
|
||||
continue;
|
||||
}
|
||||
$tenantDepartmentMap[$tenantUuid] = array_map(
|
||||
'strval',
|
||||
$departmentMapByTenantId[$tenantId] ?? []
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
'activeTenants' => $activeTenants,
|
||||
'activeRoles' => $activeRoles,
|
||||
'activeDepartments' => $activeDepartments,
|
||||
'tenantItems' => $tenantItems,
|
||||
'departments' => $departments,
|
||||
'roles' => $roles,
|
||||
'customFieldFilterDefinitions' => $customFieldFilterDefinitions,
|
||||
'customFieldFilterQuery' => $customFieldFilterQuery,
|
||||
'tenantDepartmentMap' => $tenantDepartmentMap,
|
||||
];
|
||||
}
|
||||
|
||||
public function buildGridResult(int $currentUserId, array $query): array
|
||||
{
|
||||
$limit = (int) ($query['limit'] ?? 10);
|
||||
$offset = (int) ($query['offset'] ?? 0);
|
||||
$search = trim((string) ($query['search'] ?? ''));
|
||||
$order = (string) ($query['order'] ?? 'last_name');
|
||||
$dir = (string) ($query['dir'] ?? 'asc');
|
||||
$tenant = trim((string) ($query['tenant'] ?? ''));
|
||||
$tenants = $query['tenants'] ?? '';
|
||||
$departments = $query['departments'] ?? '';
|
||||
$roles = $query['roles'] ?? '';
|
||||
$customFieldFilterSpec = $this->customFieldValueService->extractCustomFieldFilterSpec($query, $currentUserId);
|
||||
|
||||
$result = $this->userAccountService->listPaged([
|
||||
'limit' => $limit,
|
||||
'offset' => $offset,
|
||||
'search' => $search,
|
||||
'order' => $order,
|
||||
'dir' => $dir,
|
||||
'tenant' => $tenant,
|
||||
'tenants' => $tenants,
|
||||
'departments' => $departments,
|
||||
'roles' => $roles,
|
||||
'customFieldFilterSpec' => $customFieldFilterSpec,
|
||||
'tenantUserId' => $currentUserId,
|
||||
'active' => 'active',
|
||||
]);
|
||||
|
||||
$rows = [];
|
||||
foreach (($result['rows'] ?? []) as $row) {
|
||||
$tenantList = $this->normalizeLabelList($row['tenant_labels'] ?? []);
|
||||
$departmentList = $this->normalizeLabelList($row['department_labels'] ?? []);
|
||||
$roleList = $this->normalizeLabelList($row['role_labels'] ?? []);
|
||||
|
||||
$uuid = (string) ($row['uuid'] ?? '');
|
||||
$displayName = trim((string) ($row['display_name'] ?? ''));
|
||||
if ($displayName === '') {
|
||||
$displayName = trim((string) ($row['first_name'] ?? '') . ' ' . (string) ($row['last_name'] ?? ''));
|
||||
}
|
||||
if ($displayName === '') {
|
||||
$displayName = (string) ($row['email'] ?? '');
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'uuid' => $uuid,
|
||||
'display_name' => $displayName,
|
||||
'first_name' => (string) ($row['first_name'] ?? ''),
|
||||
'last_name' => (string) ($row['last_name'] ?? ''),
|
||||
'email' => (string) ($row['email'] ?? ''),
|
||||
'phone' => (string) ($row['phone'] ?? ''),
|
||||
'mobile' => (string) ($row['mobile'] ?? ''),
|
||||
'short_dial' => (string) ($row['short_dial'] ?? ''),
|
||||
'tenants' => $tenantList,
|
||||
'departments' => $departmentList,
|
||||
'roles' => $roleList,
|
||||
'has_avatar' => $uuid !== '' && $this->avatarService->hasAvatar($uuid),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'data' => $rows,
|
||||
'total' => (int) ($result['total'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
public function buildViewContext(int $currentUserId, string $uuid): array
|
||||
{
|
||||
$uuid = trim($uuid);
|
||||
if ($uuid === '') {
|
||||
return ['status' => 'invalid_uuid'];
|
||||
}
|
||||
|
||||
$user = $this->userAccountService->findByUuid($uuid);
|
||||
if (!$user || !isset($user['id'])) {
|
||||
return ['status' => 'not_found'];
|
||||
}
|
||||
|
||||
$targetUserId = (int) $user['id'];
|
||||
if ($targetUserId <= 0) {
|
||||
return ['status' => 'not_found'];
|
||||
}
|
||||
|
||||
// Users can always view their own profile; others require scope access.
|
||||
if (
|
||||
$currentUserId !== $targetUserId
|
||||
&& !$this->scopeGateway->canAccess('users', $targetUserId, $currentUserId)
|
||||
) {
|
||||
return ['status' => 'forbidden'];
|
||||
}
|
||||
|
||||
$viewerTenantIds = $this->normalizeIds($this->scopeGateway->getUserTenantIds($currentUserId));
|
||||
$assignments = $this->userAssignmentService->buildAssignmentsForUser($targetUserId);
|
||||
|
||||
$departmentMap = [];
|
||||
foreach (($assignments['departments'] ?? []) as $department) {
|
||||
if (empty($department['active'])) {
|
||||
continue;
|
||||
}
|
||||
$tenantId = (int) ($department['tenant_id'] ?? 0);
|
||||
$label = trim((string) ($department['description'] ?? ''));
|
||||
// Only show departments in tenants the viewer also belongs to — prevents leaking org structure.
|
||||
if ($tenantId <= 0 || $label === '' || !in_array($tenantId, $viewerTenantIds, true)) {
|
||||
continue;
|
||||
}
|
||||
$departmentMap[$tenantId] ??= [];
|
||||
$departmentMap[$tenantId][] = $label;
|
||||
}
|
||||
foreach ($departmentMap as $tenantId => $labels) {
|
||||
$labels = array_values(array_unique($labels));
|
||||
natcasesort($labels);
|
||||
$departmentMap[$tenantId] = array_values($labels);
|
||||
}
|
||||
|
||||
$primaryTenantId = (int) ($user['primary_tenant_id'] ?? 0);
|
||||
$tenantGroups = [];
|
||||
foreach (($assignments['tenants'] ?? []) as $tenant) {
|
||||
$tenantId = (int) ($tenant['id'] ?? 0);
|
||||
$tenantLabel = trim((string) ($tenant['description'] ?? ''));
|
||||
$status = strtolower(trim((string) ($tenant['status'] ?? '')));
|
||||
if (
|
||||
$tenantId <= 0
|
||||
|| $tenantLabel === ''
|
||||
|| $status !== 'active'
|
||||
|| !in_array($tenantId, $viewerTenantIds, true)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
$tenantGroups[] = [
|
||||
'label' => $tenantLabel,
|
||||
'is_primary' => $primaryTenantId > 0 && $tenantId === $primaryTenantId,
|
||||
'departments' => $departmentMap[$tenantId] ?? [],
|
||||
];
|
||||
}
|
||||
|
||||
usort($tenantGroups, static fn (array $a, array $b): int => strnatcasecmp(
|
||||
(string) $a['label'],
|
||||
(string) $b['label']
|
||||
));
|
||||
|
||||
$roleLabels = [];
|
||||
foreach (($assignments['roles'] ?? []) as $role) {
|
||||
if (empty($role['active'])) {
|
||||
continue;
|
||||
}
|
||||
$label = trim((string) ($role['description'] ?? ''));
|
||||
if ($label !== '') {
|
||||
$roleLabels[] = $label;
|
||||
}
|
||||
}
|
||||
$roleLabels = array_values(array_unique($roleLabels));
|
||||
natcasesort($roleLabels);
|
||||
$roleLabels = array_values($roleLabels);
|
||||
|
||||
$avatarUuid = (string) ($user['uuid'] ?? '');
|
||||
$user['has_avatar'] = $avatarUuid !== '' && $this->avatarService->hasAvatar($avatarUuid);
|
||||
$user['tenant_groups'] = $tenantGroups;
|
||||
$user['role_labels'] = $roleLabels;
|
||||
|
||||
return [
|
||||
'status' => 'ok',
|
||||
'user' => $user,
|
||||
];
|
||||
}
|
||||
|
||||
private function splitCommaValues($value): array
|
||||
{
|
||||
return array_values(array_filter(
|
||||
array_map('trim', explode(',', (string) $value)),
|
||||
static fn (string $item): bool => $item !== ''
|
||||
));
|
||||
}
|
||||
|
||||
private function normalizeDefinitions($definitions): array
|
||||
{
|
||||
if (!is_array($definitions)) {
|
||||
return [];
|
||||
}
|
||||
return array_values(array_filter(
|
||||
$definitions,
|
||||
static fn ($definition): bool => is_array($definition)
|
||||
));
|
||||
}
|
||||
|
||||
private function normalizeIds(array $values): array
|
||||
{
|
||||
$ids = array_values(array_unique(array_map('intval', $values)));
|
||||
return array_values(array_filter($ids, static fn (int $id): bool => $id > 0));
|
||||
}
|
||||
|
||||
// Accepts either a '||'-delimited DB aggregate string (from GROUP_CONCAT) or a plain array.
|
||||
private function normalizeLabelList($value): array
|
||||
{
|
||||
if (is_string($value)) {
|
||||
if ($value === '') {
|
||||
return [];
|
||||
}
|
||||
$items = explode('||', $value);
|
||||
} elseif (is_array($value)) {
|
||||
$items = $value;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_values(array_filter(array_map(
|
||||
static fn ($item): string => trim((string) $item),
|
||||
$items
|
||||
), static fn (string $item): bool => $item !== ''));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Module\AddressBook\Service;
|
||||
|
||||
use MintyPHP\Service\CustomField\CustomFieldServicesFactory;
|
||||
use MintyPHP\Service\Directory\DirectoryServicesFactory;
|
||||
use MintyPHP\Service\Tenant\TenantScopeService;
|
||||
use MintyPHP\Service\User\UserServicesFactory;
|
||||
|
||||
class AddressBookServicesFactory
|
||||
{
|
||||
private ?AddressBookService $addressBookService = null;
|
||||
|
||||
public function __construct(
|
||||
private readonly UserServicesFactory $userServicesFactory,
|
||||
private readonly DirectoryServicesFactory $directoryServicesFactory,
|
||||
private readonly CustomFieldServicesFactory $customFieldServicesFactory,
|
||||
private readonly TenantScopeService $tenantScopeService
|
||||
) {
|
||||
}
|
||||
|
||||
public function createAddressBookService(): AddressBookService
|
||||
{
|
||||
if ($this->addressBookService !== null) {
|
||||
return $this->addressBookService;
|
||||
}
|
||||
|
||||
$userFactory = $this->userServicesFactory;
|
||||
$directoryFactory = $this->directoryServicesFactory;
|
||||
return $this->addressBookService = new AddressBookService(
|
||||
$userFactory->createUserAccountService(),
|
||||
$userFactory->createUserAssignmentService(),
|
||||
$directoryFactory->createTenantService(),
|
||||
$directoryFactory->createDepartmentService(),
|
||||
$directoryFactory->createRoleService(),
|
||||
$this->tenantScopeService,
|
||||
$this->customFieldServicesFactory->createUserCustomFieldValueService(),
|
||||
$userFactory->createUserAvatarService()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user