Add a module kernel (ModuleManifest, ModuleRegistry) that allows modules to contribute routes, UI slots, search providers, layout context, session lifecycle, and permissions. Modules are activated via config/modules.php or APP_ENABLED_MODULES env variable. Conflicts (duplicate routes, permissions, slot keys) cause a fail-fast with a clear error message. Extract the address book from hardcoded Core integration points into the first module (modules/addressbook/). The module provides: - Aside icon-bar tab + People panel via UI slot system - Global search resource via AddressBookSearchProvider - Layout context data via AddressBookLayoutProvider - Session lifecycle via AddressBookSessionProvider Core cleanup removes address-book hardcodings from SearchSqlResourceProvider, SearchUiMetaProvider, SearchItemMapperProvider, appBuildLayoutNavContext(), and the aside templates. Permissions (ADDRESS_BOOK_VIEW) and business logic (AddressBookService) remain in Core as they gate general user visibility. Includes 38 new tests (894 total), PHPStan level 5 clean, and architecture tests verifying zero hardcoded address-book references in search/templates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
151 lines
10 KiB
PHP
151 lines
10 KiB
PHP
<?php
|
|
|
|
namespace MintyPHP\Support\Search;
|
|
|
|
use MintyPHP\Service\Access\PermissionService;
|
|
|
|
// Defines all SQL-backed search resources. Each resource declares count/preview/result queries
|
|
// with a {{tenantFilter}} placeholder that is substituted at query time based on the user's tenant scope.
|
|
class SearchSqlResourceProvider
|
|
{
|
|
public static function tenantScopeFilters(): array
|
|
{
|
|
return [
|
|
'users' => '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 tenant_id in (???)',
|
|
];
|
|
}
|
|
|
|
public static function resources(string $query, string $locale): array
|
|
{
|
|
$like = SearchQueryNormalizer::normalizeLikeQuery($query);
|
|
|
|
return [
|
|
[
|
|
'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' => 'scheduled-jobs',
|
|
'label' => t('Scheduled jobs'),
|
|
'permission' => PermissionService::JOBS_VIEW,
|
|
'countSql' => "select count(*) from scheduled_jobs where (job_key like ? escape '\\\\' or label like ? escape '\\\\' or description like ? escape '\\\\')",
|
|
'countParams' => [$like, $like, $like],
|
|
'previewSql' => "select id, job_key, label, enabled, next_run_at, last_run_status from scheduled_jobs where (job_key like ? escape '\\\\' or label like ? escape '\\\\' or description like ? escape '\\\\') order by label, job_key limit ?",
|
|
'previewParams' => [$like, $like, $like],
|
|
'resultSql' => "select id, job_key, label, enabled, next_run_at, last_run_status from scheduled_jobs where (job_key like ? escape '\\\\' or label like ? escape '\\\\' or description like ? escape '\\\\') order by label, job_key",
|
|
'resultParams' => [$like, $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' => 'mail-log',
|
|
'label' => t('Mail logs'),
|
|
'permission' => PermissionService::MAIL_LOG_VIEW,
|
|
'countSql' => "select count(*) from mail_log where (to_email like ? escape '\\\\' or subject like ? escape '\\\\' or template like ? escape '\\\\' or provider_message_id like ? escape '\\\\')",
|
|
'countParams' => [$like, $like, $like, $like],
|
|
'previewSql' => "select id, to_email, subject, status, created_at from mail_log where (to_email like ? escape '\\\\' or subject like ? escape '\\\\' or template like ? escape '\\\\' or provider_message_id like ? escape '\\\\') order by created_at desc limit ?",
|
|
'previewParams' => [$like, $like, $like, $like],
|
|
'resultSql' => "select id, to_email, subject, status, created_at from mail_log where (to_email like ? escape '\\\\' or subject like ? escape '\\\\' or template like ? escape '\\\\' or provider_message_id like ? escape '\\\\') order by created_at desc",
|
|
'resultParams' => [$like, $like, $like, $like],
|
|
],
|
|
[
|
|
'key' => 'api-audit',
|
|
'label' => t('API audit'),
|
|
'permission' => PermissionService::API_AUDIT_VIEW,
|
|
'countSql' => "select count(*) from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\')",
|
|
'countParams' => [$like, $like, $like, $like],
|
|
'previewSql' => "select id, request_id, method, path, status_code, created_at from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\') order by created_at desc limit ?",
|
|
'previewParams' => [$like, $like, $like, $like],
|
|
'resultSql' => "select id, request_id, method, path, status_code, created_at from api_audit_log where (request_id like ? escape '\\\\' or path like ? escape '\\\\' or error_code like ? escape '\\\\' or ip like ? escape '\\\\') order by created_at desc",
|
|
'resultParams' => [$like, $like, $like, $like],
|
|
],
|
|
[
|
|
'key' => 'system-audit',
|
|
'label' => t('System audit'),
|
|
'permission' => PermissionService::SYSTEM_AUDIT_VIEW,
|
|
'countSql' => "select count(*) from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\')",
|
|
'countParams' => [$like, $like, $like, $like],
|
|
'previewSql' => "select id, request_id, event_type, outcome, created_at from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\') order by created_at desc limit ?",
|
|
'previewParams' => [$like, $like, $like, $like],
|
|
'resultSql' => "select id, request_id, event_type, outcome, created_at from system_audit_log where (request_id like ? escape '\\\\' or event_type like ? escape '\\\\' or error_code like ? escape '\\\\' or path like ? escape '\\\\') order by created_at desc",
|
|
'resultParams' => [$like, $like, $like, $like],
|
|
],
|
|
];
|
|
}
|
|
|
|
public static function resourceKeys(): array
|
|
{
|
|
$keys = [];
|
|
foreach (self::resources('', '') as $resource) {
|
|
$key = (string) ($resource['key'] ?? '');
|
|
if ($key !== '') {
|
|
$keys[] = $key;
|
|
}
|
|
}
|
|
|
|
return $keys;
|
|
}
|
|
}
|