'and exists (select 1 from user_tenants ut where ut.user_id = users.id and ut.tenant_id in (???))', 'address-book' => 'and exists (select 1 from user_tenants ut where ut.user_id = users.id and ut.tenant_id in (???))', 'tenants' => 'and id in (???)', 'departments' => 'and exists (select 1 from tenant_departments td where td.department_id = departments.id and td.tenant_id in (???))', ]; } private static function normalizeLikeQuery(string $query): string { $query = trim($query); if ($query === '') { return ''; } $query = str_replace('\\', '\\\\', $query); $query = str_replace('_', '\\_', $query); $query = str_replace('*', '%', $query); if (!str_starts_with($query, '%')) { $query = '%' . $query; } if (!str_ends_with($query, '%')) { $query = $query . '%'; } return $query; } public static function normalizeScoreQuery(string $query): string { $query = trim($query); if ($query == '') { return ''; } return str_replace(['*', '%', '_'], '', $query); } public static function resources(string $query, string $locale): array { $like = self::normalizeLikeQuery($query); return [ [ 'key' => 'address-book', 'label' => t('Address book'), 'permission' => PermissionService::ADDRESS_BOOK_VIEW, 'countSql' => "select count(*) from users where active = 1 and (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}}", 'countParams' => [$like, $like, $like], 'previewSql' => "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 ?", 'previewParams' => [$like, $like, $like], 'resultSql' => "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", 'resultParams' => [$like, $like, $like], ], [ 'key' => 'users', 'label' => t('Users'), 'permission' => PermissionService::USERS_VIEW, 'countSql' => "select count(*) from users where (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}}", 'countParams' => [$like, $like, $like], 'previewSql' => "select uuid, first_name, last_name, email from users where (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}} order by last_name, first_name limit ?", 'previewParams' => [$like, $like, $like], 'resultSql' => "select uuid, first_name, last_name, email from users where (first_name like ? escape '\\\\' or last_name like ? escape '\\\\' or email like ? escape '\\\\') {{tenantFilter}} order by last_name, first_name", 'resultParams' => [$like, $like, $like], ], [ 'key' => 'tenants', 'label' => t('Tenants'), 'permission' => PermissionService::TENANTS_VIEW, 'countSql' => "select count(*) from tenants where description like ? escape '\\\\' {{tenantFilter}}", 'countParams' => [$like], 'previewSql' => "select uuid, description from tenants where description like ? escape '\\\\' {{tenantFilter}} order by description limit ?", 'previewParams' => [$like], 'resultSql' => "select uuid, description from tenants where description like ? escape '\\\\' {{tenantFilter}} order by description", 'resultParams' => [$like], ], [ 'key' => 'departments', 'label' => t('Departments'), 'permission' => PermissionService::DEPARTMENTS_VIEW, 'countSql' => "select count(*) from departments where description like ? escape '\\\\' {{tenantFilter}}", 'countParams' => [$like], 'previewSql' => "select uuid, description from departments where description like ? escape '\\\\' {{tenantFilter}} order by description limit ?", 'previewParams' => [$like], 'resultSql' => "select uuid, description from departments where description like ? escape '\\\\' {{tenantFilter}} order by description", 'resultParams' => [$like], ], [ 'key' => 'roles', 'label' => t('Roles'), 'permission' => PermissionService::ROLES_VIEW, 'countSql' => "select count(*) from roles where active = 1 and description like ? escape '\\\\'", 'countParams' => [$like], 'previewSql' => "select uuid, description from roles where active = 1 and description like ? escape '\\\\' order by description limit ?", 'previewParams' => [$like], 'resultSql' => "select uuid, description from roles where active = 1 and description like ? escape '\\\\' order by description", 'resultParams' => [$like], ], [ 'key' => 'permissions', 'label' => t('Permissions'), 'permission' => PermissionService::PERMISSIONS_VIEW, 'countSql' => "select count(*) from permissions where active = 1 and (`key` like ? escape '\\\\' or description like ? escape '\\\\')", 'countParams' => [$like, $like], 'previewSql' => "select id, description, `key` from permissions where active = 1 and (`key` like ? escape '\\\\' or description like ? escape '\\\\') order by `key` limit ?", 'previewParams' => [$like, $like], 'resultSql' => "select id, description, `key` from permissions where active = 1 and (`key` like ? escape '\\\\' or description like ? escape '\\\\') order by `key`", 'resultParams' => [$like, $like], ], [ 'key' => 'pages', 'label' => t('Pages'), 'permission' => '', 'countSql' => "select count(*) from pages p join page_contents pc on pc.page_id = p.id and pc.locale = ? where p.slug like ? escape '\\\\' or pc.content like ? escape '\\\\'", 'countParams' => [$locale, $like, $like], 'previewSql' => "select p.slug from pages p join page_contents pc on pc.page_id = p.id and pc.locale = ? where p.slug like ? escape '\\\\' or pc.content like ? escape '\\\\' order by p.slug limit ?", 'previewParams' => [$locale, $like, $like], 'resultSql' => "select p.slug from pages p join page_contents pc on pc.page_id = p.id and pc.locale = ? where p.slug like ? escape '\\\\' or pc.content like ? escape '\\\\' order by p.slug", 'resultParams' => [$locale, $like, $like], ], [ 'key' => 'settings', 'label' => t('Settings'), 'permission' => PermissionService::SETTINGS_VIEW, 'countSql' => "select count(*) from settings where `key` like ? escape '\\\\' or value like ? escape '\\\\'", 'countParams' => [$like, $like], 'previewSql' => null, 'previewParams' => [], 'resultSql' => "select `key`, value from settings where `key` like ? escape '\\\\' or value like ? escape '\\\\' order by `key`", 'resultParams' => [$like, $like], ], ]; } public static function iconForKey(string $key): string { return match ($key) { 'address-book' => 'bi-people', 'users' => 'bi-person-badge', 'tenants' => 'bi-buildings', 'departments' => 'bi-diagram-3', 'roles' => 'bi-people', 'permissions' => 'bi-shield-lock', 'pages' => 'bi-file-text', 'settings' => 'bi-gear', default => 'bi-search', }; } public static function listUrl(string $key, string $query): string { $encoded = urlencode($query); return match ($key) { 'address-book' => lurl('address-book') . '?search=' . $encoded, 'users' => lurl('admin/users') . '?search=' . $encoded, 'tenants' => lurl('admin/tenants') . '?search=' . $encoded, 'departments' => lurl('admin/departments') . '?search=' . $encoded, 'roles' => lurl('admin/roles') . '?search=' . $encoded, 'permissions' => lurl('admin/permissions') . '?search=' . $encoded, 'pages' => lurl(''), 'settings' => lurl('admin/settings'), default => lurl(''), }; } public static function mapPreviewItem(string $key, array $data, string $query): ?array { if ($key === 'users') { $label = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? '')); $email = trim((string) ($data['email'] ?? '')); if ($email !== '') { $label = $label === '' ? $email : ($label . ' — ' . $email); } $url = lurl('admin/users/edit/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query); } elseif ($key === 'address-book') { $label = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? '')); $email = trim((string) ($data['email'] ?? '')); if ($email !== '') { $label = $label === '' ? $email : ($label . ' — ' . $email); } $url = lurl('address-book/view/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query); } elseif ($key === 'permissions') { $label = (string) ($data['description'] ?? ''); $url = lurl('admin/permissions/edit/' . ($data['id'] ?? '')) . '?search=' . urlencode($query); } elseif ($key === 'pages') { $label = (string) ($data['slug'] ?? ''); $url = lurl('page/' . $label) . '?search=' . urlencode($query); } else { $label = (string) ($data['description'] ?? ''); $url = lurl('admin/' . $key . '/edit/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query); } if ($label === '' || $url === '') { return null; } return ['label' => $label, 'url' => $url]; } public static function mapResultItem(string $key, string $label, array $data): ?array { $title = ''; $description = ''; $url = ''; $image = ''; if ($key === 'users') { $title = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? '')); $description = (string) ($data['email'] ?? ''); $url = lurl('admin/users/edit/' . ($data['uuid'] ?? '')); } elseif ($key === 'address-book') { $title = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? '')); $description = (string) ($data['email'] ?? ''); $uuid = (string) ($data['uuid'] ?? ''); $url = lurl('address-book/view/' . $uuid); if ($uuid !== '' && UserAvatarService::hasAvatar($uuid)) { $image = lurl('admin/users/avatar-file') . '?uuid=' . urlencode($uuid) . '&size=64'; } } elseif ($key === 'permissions') { $title = (string) ($data['key'] ?? ''); $description = (string) ($data['description'] ?? ''); $url = lurl('admin/permissions/edit/' . ($data['id'] ?? '')); } elseif ($key === 'pages') { $title = (string) ($data['slug'] ?? ''); $description = t('Page'); $url = lurl('page/' . $title); } elseif ($key === 'settings') { $title = (string) ($data['key'] ?? ''); $description = (string) ($data['value'] ?? ''); $url = lurl('admin/settings'); } else { $title = (string) ($data['description'] ?? ''); $description = $label; $url = lurl('admin/' . $key . '/edit/' . ($data['uuid'] ?? '')); } if ($title === '' || $url === '') { return null; } return [ 'type' => $label, 'title' => $title, 'description' => $description, 'url' => $url, 'image' => $image, 'icon' => self::iconForKey($key), ]; } }