[ 'title' => $r['name'], 'detail' => implode( ' · ', array_filter([$r['internal_code'], $r['identification_number'], $r['species'], $r['status']]), ), 'url' => '/animal?id=' . (int) $r['id'], ], ); } if (PermissionService::can('directory')) { $out['directory'] = self::rows( $db, "SELECT id,name,kind,phone,email,city FROM directory_contacts WHERE deleted_at IS NULL AND (search_fold(name) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(phone,'')) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(email,'')) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(city,'')) LIKE :q ESCAPE '\\') ORDER BY name COLLATE NOCASE LIMIT :lim", $like, $limit, static fn($r) => [ 'title' => $r['name'], 'detail' => implode(' · ', array_filter([$r['city'], $r['phone'], $r['email']])), 'url' => '/directory/view?id=' . (int) $r['id'], ], ); } if (PermissionService::can('agenda')) { $out['agenda'] = self::rows( $db, "SELECT id,title,starts_at,location,status FROM agenda_items WHERE search_fold(title) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(description,'')) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(location,'')) LIKE :q ESCAPE '\\' ORDER BY starts_at DESC LIMIT :lim", $like, $limit, static fn($r) => [ 'title' => $r['title'], 'detail' => implode( ' · ', array_filter([substr((string) $r['starts_at'], 0, 16), $r['location'], $r['status']]), ), 'url' => '/agenda?q=' . rawurlencode($r['title']), ], ); } if (PermissionService::can('accounting')) { $out['accounting'] = self::rows( $db, "SELECT ai.id,ai.reference,ai.invoice_date,ai.status,dc.name vendor FROM accounting_invoices ai LEFT JOIN directory_contacts dc ON dc.id=ai.vendor_contact_id WHERE ai.deleted_at IS NULL AND (search_fold(ai.reference) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(ai.notes,'')) LIKE :q ESCAPE '\\' OR search_fold(COALESCE(dc.name,'')) LIKE :q ESCAPE '\\') ORDER BY ai.invoice_date DESC LIMIT :lim", $like, $limit, static fn($r) => [ 'title' => $r['reference'], 'detail' => implode(' · ', array_filter([$r['vendor'], $r['invoice_date'], $r['status']])), 'url' => '/accounting/edit?id=' . (int) $r['id'], ], ); } return array_filter($out); } private static function fold(string $value): string { $value = mb_strtolower(trim($value), 'UTF-8'); $value = strtr($value, ['œ' => 'oe', 'æ' => 'ae', 'ø' => 'o', 'ß' => 'ss']); $ascii = @iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $value); return $ascii === false ? $value : strtolower($ascii); } private static function rows(PDO $db, string $sql, string $like, int $limit, callable $map): array { $s = $db->prepare($sql); $s->bindValue(':q', $like); $s->bindValue(':lim', max(1, min(20, $limit)), PDO::PARAM_INT); $s->execute(); return array_map($map, $s->fetchAll(PDO::FETCH_ASSOC)); } }