refactor: rename lib/ to core/ for clearer core-module separation
Rename the top-level lib/ directory to core/ so the project root immediately communicates which code is core platform and which lives in modules/. PHP namespaces (MintyPHP\*) are unchanged — only the Composer PSR-4 path mapping moves from lib/ to core/. Module-internal lib/ directories (modules/*/lib/) are untouched. Updated across the full stack: - composer.json PSR-4 mapping - bootstrap entry points (web/index.php, bin/*, tests/bootstrap.php) - tooling configs (phpstan.neon, phpunit.xml, php-cs-fixer) - 26 architecture contract tests - enforcement-policy, guard-catalog, quality-gates - all documentation (CLAUDE.md, README, 25 docs/, .agents/skills/) - bin/qa-extended.sh search paths - .claude/settings.local.json permission paths Workflow: .agents/runs/CORE-LIB-RENAME-001/ (Analyst → Planner → Executor → Code Review (4 findings fixed) → Security Review → Acceptance Test → Finalizer) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
121
core/Support/Search/SearchItemMapperProvider.php
Normal file
121
core/Support/Search/SearchItemMapperProvider.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Support\Search;
|
||||
|
||||
class SearchItemMapperProvider
|
||||
{
|
||||
public static function mapPreviewItem(string $key, array $data, string $query): ?array
|
||||
{
|
||||
$subtitle = '';
|
||||
|
||||
if ($key === 'users') {
|
||||
$label = trim(($data['first_name'] ?? '') . ' ' . ($data['last_name'] ?? ''));
|
||||
$subtitle = trim((string) ($data['email'] ?? ''));
|
||||
if ($label === '' && $subtitle !== '') {
|
||||
$label = $subtitle;
|
||||
$subtitle = '';
|
||||
}
|
||||
$url = lurl('admin/users/edit/' . ($data['uuid'] ?? '')) . '?search=' . urlencode($query);
|
||||
} elseif ($key === 'permissions') {
|
||||
$label = (string) ($data['description'] ?? '');
|
||||
$url = lurl('admin/permissions/edit/' . ($data['id'] ?? '')) . '?search=' . urlencode($query);
|
||||
} elseif ($key === 'scheduled-jobs') {
|
||||
$jobLabel = trim((string) ($data['label'] ?? ''));
|
||||
$jobKey = trim((string) ($data['job_key'] ?? ''));
|
||||
$status = trim((string) ($data['last_run_status'] ?? ''));
|
||||
$label = $jobLabel !== '' ? $jobLabel : $jobKey;
|
||||
if ($status !== '') {
|
||||
$subtitle = strtoupper($status);
|
||||
}
|
||||
$url = lurl('admin/scheduled-jobs/edit/' . ($data['id'] ?? '')) . '?search=' . urlencode($query);
|
||||
} elseif ($key === 'mail-log') {
|
||||
$toEmail = trim((string) ($data['to_email'] ?? ''));
|
||||
$subject = trim((string) ($data['subject'] ?? ''));
|
||||
$status = trim((string) ($data['status'] ?? ''));
|
||||
$label = $subject !== '' ? $subject : $toEmail;
|
||||
$subtitleParts = [];
|
||||
if ($status !== '') {
|
||||
$subtitleParts[] = strtoupper($status);
|
||||
}
|
||||
if ($toEmail !== '' && $subject !== '') {
|
||||
$subtitleParts[] = $toEmail;
|
||||
}
|
||||
$subtitle = implode(' — ', $subtitleParts);
|
||||
$url = lurl('admin/mail-log/view/' . ($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 === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = ['label' => $label, 'url' => $url];
|
||||
if ($subtitle !== '') {
|
||||
$item['subtitle'] = $subtitle;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
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 === 'permissions') {
|
||||
$title = (string) ($data['key'] ?? '');
|
||||
$description = (string) ($data['description'] ?? '');
|
||||
$url = lurl('admin/permissions/edit/' . ($data['id'] ?? ''));
|
||||
} elseif ($key === 'scheduled-jobs') {
|
||||
$jobLabel = trim((string) ($data['label'] ?? ''));
|
||||
$jobKey = trim((string) ($data['job_key'] ?? ''));
|
||||
$title = $jobLabel !== '' ? $jobLabel : $jobKey;
|
||||
$status = trim((string) ($data['last_run_status'] ?? ''));
|
||||
$description = $status !== '' ? strtoupper($status) : ($jobKey !== '' ? $jobKey : '');
|
||||
$url = lurl('admin/scheduled-jobs/edit/' . ($data['id'] ?? ''));
|
||||
} elseif ($key === 'pages') {
|
||||
$title = (string) ($data['slug'] ?? '');
|
||||
$description = t('Page');
|
||||
$url = lurl('page/' . $title);
|
||||
} elseif ($key === 'mail-log') {
|
||||
$title = trim((string) ($data['subject'] ?? ''));
|
||||
if ($title === '') {
|
||||
$title = trim((string) ($data['to_email'] ?? ''));
|
||||
}
|
||||
$status = trim((string) ($data['status'] ?? ''));
|
||||
$toEmail = trim((string) ($data['to_email'] ?? ''));
|
||||
$description = trim(($status !== '' ? strtoupper($status) : '') . ($toEmail !== '' ? ' — ' . $toEmail : ''));
|
||||
$url = lurl('admin/mail-log/view/' . ($data['id'] ?? ''));
|
||||
} 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' => SearchUiMetaProvider::iconForKey($key),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
40
core/Support/Search/SearchQueryNormalizer.php
Normal file
40
core/Support/Search/SearchQueryNormalizer.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Support\Search;
|
||||
|
||||
class SearchQueryNormalizer
|
||||
{
|
||||
// Prepares a user query for SQL LIKE. Escapes \ and _ (LIKE special chars),
|
||||
// converts * to %, and wraps with % wildcards if not already present.
|
||||
public 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 .= '%';
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
// Strips wildcard chars for relevance scoring — score queries need a clean term, not a LIKE pattern.
|
||||
public static function normalizeScoreQuery(string $query): string
|
||||
{
|
||||
$query = trim($query);
|
||||
if ($query === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return str_replace(['*', '%', '_'], '', $query);
|
||||
}
|
||||
}
|
||||
253
core/Support/Search/SearchSpecialResourceProvider.php
Normal file
253
core/Support/Search/SearchSpecialResourceProvider.php
Normal file
@@ -0,0 +1,253 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Support\Search;
|
||||
|
||||
use MintyPHP\Service\Docs\DocsCatalogService;
|
||||
use MintyPHP\Service\Ui\HotkeyService;
|
||||
|
||||
class SearchSpecialResourceProvider
|
||||
{
|
||||
private static function hotkeyEntries(): array
|
||||
{
|
||||
$rows = [];
|
||||
foreach (HotkeyService::list() as $row) {
|
||||
$actionKey = trim($row['action_key']);
|
||||
$mac = trim($row['mac']);
|
||||
$win = trim($row['win']);
|
||||
if ($actionKey === '' || $mac === '' || $win === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows[] = [
|
||||
'label' => t($actionKey),
|
||||
'mac' => $mac,
|
||||
'win' => $win,
|
||||
];
|
||||
}
|
||||
|
||||
return $rows;
|
||||
}
|
||||
|
||||
// Two-tier match: first checks against the hotkey service's keyword list (e.g. "shortcut", "keyboard"),
|
||||
// then falls back to substring matching against individual entry labels/key combos.
|
||||
private static function matchesHotkeyQueryWith(string $query, array $entries): bool
|
||||
{
|
||||
$query = trim(mb_strtolower($query));
|
||||
if ($query === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tokens = preg_split('/[^a-z0-9]+/i', $query);
|
||||
$tokens = is_array($tokens) ? array_values(array_filter($tokens, static fn ($part): bool => $part !== '')) : [];
|
||||
|
||||
foreach (HotkeyService::searchKeywords() as $keywordRaw) {
|
||||
$keyword = trim(mb_strtolower((string) $keywordRaw));
|
||||
if ($keyword === '') {
|
||||
continue;
|
||||
}
|
||||
if (in_array($keyword, $tokens, true)) {
|
||||
return true;
|
||||
}
|
||||
foreach ($tokens as $token) {
|
||||
if (strlen($token) >= 3 && str_starts_with($keyword, $token)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (strlen($query) >= 3 && str_starts_with($keyword, $query)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$label = mb_strtolower((string) ($entry['label'] ?? ''));
|
||||
$mac = mb_strtolower((string) ($entry['mac'] ?? ''));
|
||||
$win = mb_strtolower((string) ($entry['win'] ?? ''));
|
||||
if (($label !== '' && str_contains($label, $query))
|
||||
|| ($mac !== '' && str_contains($mac, $query))
|
||||
|| ($win !== '' && str_contains($win, $query))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function filterHotkeyEntries(string $query): array
|
||||
{
|
||||
$needle = trim(mb_strtolower($query));
|
||||
$entries = self::hotkeyEntries();
|
||||
|
||||
if (!self::matchesHotkeyQueryWith($query, $entries)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($needle === '') {
|
||||
return $entries;
|
||||
}
|
||||
|
||||
$filtered = [];
|
||||
foreach ($entries as $entry) {
|
||||
$label = mb_strtolower((string) ($entry['label'] ?? ''));
|
||||
$mac = mb_strtolower((string) ($entry['mac'] ?? ''));
|
||||
$win = mb_strtolower((string) ($entry['win'] ?? ''));
|
||||
if (str_contains($label, $needle) || str_contains($mac, $needle) || str_contains($win, $needle)) {
|
||||
$filtered[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
return $filtered;
|
||||
}
|
||||
|
||||
public static function hotkeyPreviewResource(string $query): ?array
|
||||
{
|
||||
$entries = self::filterHotkeyEntries($query);
|
||||
if ($entries === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($entries as $entry) {
|
||||
$items[] = [
|
||||
'label' => trim((string) ($entry['label'] ?? '') . ' - ' . (string) ($entry['mac'] ?? '') . ' / ' . (string) ($entry['win'] ?? '')),
|
||||
'url' => lurl('help/hotkeys'),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => 'hotkeys',
|
||||
'label' => t('Keyboard shortcuts'),
|
||||
'count' => count($items),
|
||||
'url' => lurl('help/hotkeys'),
|
||||
'items' => array_slice($items, 0, 5),
|
||||
];
|
||||
}
|
||||
|
||||
public static function hotkeyResultItems(string $query): array
|
||||
{
|
||||
$entries = self::filterHotkeyEntries($query);
|
||||
if ($entries === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$result = [];
|
||||
foreach ($entries as $entry) {
|
||||
$result[] = [
|
||||
'type' => t('Keyboard shortcuts'),
|
||||
'title' => (string) ($entry['label'] ?? ''),
|
||||
'description' => trim((string) ($entry['mac'] ?? '') . ' / ' . (string) ($entry['win'] ?? '')),
|
||||
'url' => lurl('help/hotkeys'),
|
||||
'image' => '',
|
||||
'icon' => 'bi-keyboard',
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private static function docsResultUrl(string $slug, string $anchor = ''): string
|
||||
{
|
||||
$url = lurl('admin/docs/' . $slug);
|
||||
if ($anchor !== '') {
|
||||
$url .= '#' . $anchor;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public static function docsPreviewResource(string $query): ?array
|
||||
{
|
||||
$matches = DocsCatalogService::search($query);
|
||||
if ($matches === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$seenDocs = [];
|
||||
$previewItems = [];
|
||||
foreach ($matches as $match) {
|
||||
$slug = trim((string) $match['slug']);
|
||||
if ($slug === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$seenDocs[$slug] = true;
|
||||
if (isset($previewItems[$slug])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$title = trim((string) $match['title']);
|
||||
if ($title === '') {
|
||||
$title = $slug;
|
||||
}
|
||||
$section = trim((string) $match['section']);
|
||||
$label = $title;
|
||||
if ($section !== '' && mb_strtolower($section) !== mb_strtolower($title)) {
|
||||
$label .= ' - ' . $section;
|
||||
}
|
||||
|
||||
$previewItems[$slug] = [
|
||||
'label' => $label,
|
||||
'url' => self::docsResultUrl($slug, (string) $match['anchor']),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'key' => 'docs',
|
||||
'label' => t('Documentation'),
|
||||
'count' => count($seenDocs),
|
||||
'url' => lurl('admin/docs/' . DocsCatalogService::defaultSlug()) . '?search=' . urlencode($query),
|
||||
'items' => array_values(array_slice($previewItems, 0, 5)),
|
||||
];
|
||||
}
|
||||
|
||||
public static function docsResultItems(string $query): array
|
||||
{
|
||||
$matches = DocsCatalogService::search($query);
|
||||
if ($matches === []) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($matches as $match) {
|
||||
$slug = trim((string) $match['slug']);
|
||||
$title = trim((string) $match['title']);
|
||||
if ($slug === '' || $title === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$section = trim((string) $match['section']);
|
||||
$snippet = trim((string) $match['snippet']);
|
||||
$descriptionParts = [];
|
||||
if ($section !== '' && mb_strtolower($section) !== mb_strtolower($title)) {
|
||||
$descriptionParts[] = $section;
|
||||
}
|
||||
if ($snippet !== '') {
|
||||
$descriptionParts[] = $snippet;
|
||||
}
|
||||
|
||||
// Map raw doc scores (0–1000+) to coarser search_score buckets for result ranking.
|
||||
$rawScore = (int) $match['score'];
|
||||
$searchScore = match (true) {
|
||||
$rawScore >= 1000 => 400,
|
||||
$rawScore >= 900 => 360,
|
||||
$rawScore >= 800 => 330,
|
||||
$rawScore >= 700 => 300,
|
||||
$rawScore >= 650 => 260,
|
||||
$rawScore >= 600 => 230,
|
||||
$rawScore >= 500 => 200,
|
||||
default => 150,
|
||||
};
|
||||
|
||||
$items[] = [
|
||||
'type' => t('Documentation'),
|
||||
'title' => $title,
|
||||
'description' => implode(' - ', $descriptionParts),
|
||||
'url' => self::docsResultUrl($slug, (string) $match['anchor']),
|
||||
'image' => '',
|
||||
'icon' => 'bi-journal-text',
|
||||
'search_score' => $searchScore,
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
128
core/Support/Search/SearchSqlResourceProvider.php
Normal file
128
core/Support/Search/SearchSqlResourceProvider.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?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],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public static function resourceKeys(): array
|
||||
{
|
||||
$keys = [];
|
||||
foreach (self::resources('', '') as $resource) {
|
||||
$key = (string) ($resource['key'] ?? '');
|
||||
if ($key !== '') {
|
||||
$keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
}
|
||||
61
core/Support/Search/SearchUiMetaProvider.php
Normal file
61
core/Support/Search/SearchUiMetaProvider.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace MintyPHP\Support\Search;
|
||||
|
||||
class SearchUiMetaProvider
|
||||
{
|
||||
private const ICONS = [
|
||||
'users' => 'bi-person-badge',
|
||||
'tenants' => 'bi-buildings',
|
||||
'departments' => 'bi-diagram-3',
|
||||
'roles' => 'bi-people',
|
||||
'permissions' => 'bi-shield-lock',
|
||||
'scheduled-jobs' => 'bi-calendar-check',
|
||||
'pages' => 'bi-file-text',
|
||||
'mail-log' => 'bi-envelope-paper',
|
||||
'docs' => 'bi-journal-text',
|
||||
];
|
||||
|
||||
private const LIST_URL_KEYS = [
|
||||
'users',
|
||||
'tenants',
|
||||
'departments',
|
||||
'roles',
|
||||
'permissions',
|
||||
'scheduled-jobs',
|
||||
'pages',
|
||||
'mail-log',
|
||||
];
|
||||
|
||||
public static function iconForKey(string $key): string
|
||||
{
|
||||
return self::ICONS[$key] ?? 'bi-search';
|
||||
}
|
||||
|
||||
public static function listUrl(string $key, string $query): string
|
||||
{
|
||||
$encoded = urlencode($query);
|
||||
|
||||
return match ($key) {
|
||||
'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,
|
||||
'scheduled-jobs' => lurl('admin/scheduled-jobs') . '?search=' . $encoded,
|
||||
'pages' => lurl(''),
|
||||
'mail-log' => lurl('admin/mail-log') . '?search=' . $encoded,
|
||||
default => lurl(''),
|
||||
};
|
||||
}
|
||||
|
||||
public static function hasIconForKey(string $key): bool
|
||||
{
|
||||
return array_key_exists($key, self::ICONS);
|
||||
}
|
||||
|
||||
public static function hasListUrlForKey(string $key): bool
|
||||
{
|
||||
return in_array($key, self::LIST_URL_KEYS, true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user