1
0
Files
breadcrumb-the-shire/modules/addressbook/lib/Module/AddressBook/Service/AddressBookService.php
fs 6c99c040b2 refactor(support): centralize ID-array normalization in toIntIds()
Consolidates the scattered `array_values(array_unique(array_map('intval',
$x)))` + manual positive-filter pattern behind a single lenient helper
`toIntIds(mixed $value): array` in core/Support/helpers/array.php.

- `RepositoryArrayHelper::sanitizePositiveIds()` now delegates (keeps
  strict array-input contract + existing tests intact).
- Drops two private duplicates: `UserProfileViewService::normalizeIds()`
  and `AddressBookService::normalizeIds()`.
- Replaces 12 inline occurrences across admin action pages with the
  helper, cutting boilerplate by 3-5 lines per site.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:11:53 +02:00

341 lines
13 KiB
PHP

<?php
namespace MintyPHP\Module\AddressBook\Service;
use MintyPHP\Repository\CustomField\TenantCustomFieldOptionRepositoryInterface;
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\UserAvatarService;
use MintyPHP\Service\User\UserProfileViewService;
class AddressBookService
{
public function __construct(
private readonly UserAccountService $userAccountService,
private readonly TenantService $tenantService,
private readonly DepartmentService $departmentService,
private readonly RoleService $roleService,
private readonly TenantScopeService $scopeGateway,
private readonly UserCustomFieldValueService $customFieldValueService,
private readonly UserAvatarService $avatarService,
private readonly UserProfileViewService $userProfileViewService,
private readonly TenantCustomFieldOptionRepositoryInterface $tenantCustomFieldOptionRepository
) {
}
public function buildIndexContext(int $currentUserId, array $query): array
{
$activeTenants = $this->splitCommaValues($query['tenants'] ?? '');
$activeRoles = $this->splitCommaValues($query['roles'] ?? '');
$activeDepartments = $this->splitCommaValues($query['departments'] ?? '');
$tenantIds = toIntIds($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 = $this->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] ?? []
);
}
$activeTenant = trim((string) ($query['tenant'] ?? ''));
$tenantTabs = array_values(array_filter(array_map(
static function (array $tenant): array {
return [
'uuid' => (string) ($tenant['uuid'] ?? ''),
'description' => (string) ($tenant['description'] ?? ''),
];
},
$tenants
), static fn (array $tab): bool => $tab['uuid'] !== ''));
usort($tenantTabs, static fn (array $a, array $b): int => strnatcasecmp(
(string) $a['description'],
(string) $b['description']
));
return [
'activeTenants' => $activeTenants,
'activeRoles' => $activeRoles,
'activeDepartments' => $activeDepartments,
'activeTenant' => $activeTenant,
'tenantItems' => $tenantItems,
'tenantTabs' => $tenantTabs,
'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'] ?? '');
}
$primaryTenantLabel = $tenantList[0] ?? '';
$primaryDepartmentLabel = $departmentList[0] ?? '';
$extraAssignmentsCount = max(0, count($tenantList) - 1) + max(0, count($departmentList) - 1);
$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,
'primary_tenant_label' => $primaryTenantLabel,
'primary_department_label' => $primaryDepartmentLabel,
'extra_assignments_count' => $extraAssignmentsCount,
'has_avatar' => $uuid !== '' && $this->avatarService->hasAvatar($uuid),
];
}
return [
'data' => $rows,
'total' => (int) ($result['total'] ?? 0),
];
}
public function buildViewContext(int $currentUserId, string $uuid): array
{
return $this->userProfileViewService->buildProfile($currentUserId, $uuid);
}
/**
* Transform custom field filter definitions into chip metadata for the grid UI.
*
* @param list<array<string, mixed>> $definitions
* @return list<array<string, mixed>>
*/
public function buildCustomFieldChipMeta(array $definitions): array
{
$chips = [];
foreach ($definitions as $definition) {
$uuid = strtolower(trim((string) ($definition['uuid'] ?? '')));
$type = strtolower(trim((string) ($definition['type'] ?? '')));
$label = trim((string) ($definition['label'] ?? ''));
$options = is_array($definition['options'] ?? null) ? $definition['options'] : [];
if ($uuid === '' || $type === '' || $label === '') {
continue;
}
if (in_array($type, ['select', 'boolean'], true)) {
$optionMap = gridOptionMapFromItems($options);
if ($type === 'boolean') {
$optionMap = ['1' => t('Yes'), '0' => t('No')];
}
$chips[] = [
'type' => 'scalar',
'param' => 'cf_' . $uuid,
'label' => $label,
'options' => $optionMap,
];
continue;
}
if ($type === 'multiselect') {
$chips[] = [
'type' => 'multi_csv',
'param' => 'cfm_' . $uuid,
'label' => $label,
'options' => gridOptionMapFromItems($options),
];
continue;
}
if ($type === 'date') {
$chips[] = [
'type' => 'date_range',
'label' => $label,
'from_param' => 'cfd_' . $uuid . '_from',
'to_param' => 'cfd_' . $uuid . '_to',
];
}
}
return $chips;
}
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)
));
}
// 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 !== ''));
}
}