164 lines
5.0 KiB
PHP
164 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\Card;
|
|
use App\Models\User;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Str;
|
|
use Sabre\VObject\Reader;
|
|
|
|
class AttendeeController extends Controller
|
|
{
|
|
public function suggest(Request $request)
|
|
{
|
|
$q = trim((string) $request->input('q', $request->input('attendee', '')));
|
|
if ($q === '' || mb_strlen($q) < 2) {
|
|
return response()->view('event.partials.attendee-suggestions', ['suggestions' => []]);
|
|
}
|
|
|
|
$principal = $request->user()?->principal_uri;
|
|
if (!$principal) {
|
|
return response()->view('event.partials.attendee-suggestions', ['suggestions' => []]);
|
|
}
|
|
|
|
$query = Str::lower($q);
|
|
$contacts = [];
|
|
$seen = [];
|
|
|
|
$cards = Card::query()
|
|
->select('cards.carddata')
|
|
->join('addressbooks', 'addressbooks.id', '=', 'cards.addressbookid')
|
|
->where('addressbooks.principaluri', $principal)
|
|
->whereRaw('LOWER(cards.carddata) LIKE ?', ['%' . $query . '%'])
|
|
->orderByDesc('cards.lastmodified')
|
|
->limit(200)
|
|
->get();
|
|
|
|
foreach ($cards as $card) {
|
|
$parsed = $this->parseCardEmails((string) $card->carddata);
|
|
$name = $parsed['name'];
|
|
|
|
foreach ($parsed['emails'] as $email) {
|
|
$emailLc = Str::lower($email);
|
|
$matches = str_contains($emailLc, $query)
|
|
|| ($name !== '' && str_contains(Str::lower($name), $query));
|
|
|
|
if (!$matches || isset($seen[$emailLc])) {
|
|
continue;
|
|
}
|
|
|
|
$seen[$emailLc] = true;
|
|
$contacts[] = [
|
|
'email' => $emailLc,
|
|
'name' => $name !== '' ? $name : null,
|
|
'attendee_uri' => 'mailto:' . $emailLc,
|
|
'source' => 'contact',
|
|
];
|
|
}
|
|
}
|
|
|
|
if (filter_var($q, FILTER_VALIDATE_EMAIL)) {
|
|
$emailLc = Str::lower($q);
|
|
if (!isset($seen[$emailLc])) {
|
|
$contacts[] = [
|
|
'email' => $emailLc,
|
|
'name' => null,
|
|
'attendee_uri' => 'mailto:' . $emailLc,
|
|
'source' => 'email',
|
|
];
|
|
}
|
|
}
|
|
|
|
if (empty($contacts)) {
|
|
return response()->view('event.partials.attendee-suggestions', ['suggestions' => []]);
|
|
}
|
|
|
|
usort($contacts, function (array $a, array $b) use ($query) {
|
|
$scoreA = $this->score($a, $query);
|
|
$scoreB = $this->score($b, $query);
|
|
|
|
if ($scoreA !== $scoreB) {
|
|
return $scoreA <=> $scoreB;
|
|
}
|
|
|
|
return strcmp($a['email'], $b['email']);
|
|
});
|
|
|
|
$contacts = array_slice($contacts, 0, 8);
|
|
$emails = array_values(array_unique(array_map(fn ($item) => $item['email'], $contacts)));
|
|
$knownUsers = User::query()
|
|
->whereIn('email', $emails)
|
|
->pluck('email')
|
|
->map(fn ($email) => Str::lower((string) $email))
|
|
->flip()
|
|
->all();
|
|
|
|
foreach ($contacts as &$item) {
|
|
$item['verified'] = isset($knownUsers[$item['email']]);
|
|
}
|
|
unset($item);
|
|
|
|
return response()->view('event.partials.attendee-suggestions', [
|
|
'suggestions' => $contacts,
|
|
]);
|
|
}
|
|
|
|
private function parseCardEmails(string $carddata): array
|
|
{
|
|
$name = '';
|
|
$emails = [];
|
|
|
|
try {
|
|
$vcard = Reader::read($carddata);
|
|
$name = trim((string) ($vcard->FN ?? ''));
|
|
foreach ($vcard->select('EMAIL') as $emailProp) {
|
|
$email = Str::lower(trim((string) $emailProp->getValue()));
|
|
if ($email !== '') {
|
|
$emails[] = $email;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
if (preg_match('/^FN:(.+)$/mi', $carddata, $fn)) {
|
|
$name = trim($fn[1]);
|
|
}
|
|
|
|
if (preg_match_all('/^EMAIL[^:]*:(.+)$/mi', $carddata, $matches)) {
|
|
foreach (($matches[1] ?? []) as $emailRaw) {
|
|
$email = Str::lower(trim((string) $emailRaw));
|
|
if ($email !== '') {
|
|
$emails[] = $email;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return [
|
|
'name' => $name,
|
|
'emails' => array_values(array_unique($emails)),
|
|
];
|
|
}
|
|
|
|
private function score(array $item, string $query): int
|
|
{
|
|
$email = Str::lower((string) ($item['email'] ?? ''));
|
|
$name = Str::lower((string) ($item['name'] ?? ''));
|
|
$source = (string) ($item['source'] ?? '');
|
|
|
|
if ($email === $query) {
|
|
return 0;
|
|
}
|
|
if ($email !== '' && str_starts_with($email, $query)) {
|
|
return 1;
|
|
}
|
|
if ($name !== '' && str_starts_with($name, $query)) {
|
|
return 2;
|
|
}
|
|
if ($source === 'contact') {
|
|
return 3;
|
|
}
|
|
|
|
return 4;
|
|
}
|
|
}
|