Publication initiale de RelaisSMS : documentation et sources nettoyees
This commit is contained in:
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Contracts\PhoneNumberNormalizerInterface;
|
||||
use App\Models\Contact;
|
||||
use App\Models\ContactGroup;
|
||||
use App\Models\SourceSyncRun;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class ContactController extends Controller
|
||||
{
|
||||
public function index(Request $request): View
|
||||
{
|
||||
$search = (string) $request->query('q', '');
|
||||
$groupId = $request->integer('group');
|
||||
$favoriteOnly = $request->boolean('favorite');
|
||||
$selectedStatus = (string) $request->query('status', '');
|
||||
$selectedMobileFilter = (string) $request->query('mobile', 'detected');
|
||||
|
||||
if (! in_array($selectedStatus, ['', 'active', 'favorite', 'opt_out'], true)) {
|
||||
$selectedStatus = '';
|
||||
}
|
||||
|
||||
if (! in_array($selectedMobileFilter, ['all', 'detected', 'non_mobile', 'invalid', 'missing'], true)) {
|
||||
$selectedMobileFilter = 'detected';
|
||||
}
|
||||
|
||||
$contacts = Contact::query()
|
||||
->with(['groups', 'phoneNumbers'])
|
||||
->withCount('groups')
|
||||
->search($search)
|
||||
->when($groupId > 0, function ($query) use ($groupId): void {
|
||||
$query->whereHas('groups', fn ($query) => $query->whereKey($groupId));
|
||||
})
|
||||
->when($favoriteOnly || $selectedStatus === 'favorite', function ($query): void {
|
||||
$query->where('is_favorite', true);
|
||||
})
|
||||
->when($selectedStatus === 'opt_out', function ($query): void {
|
||||
$query->where('sms_opt_out', true);
|
||||
})
|
||||
->when($selectedStatus === 'active', function ($query): void {
|
||||
$query->where('sms_opt_out', false);
|
||||
})
|
||||
->when($selectedMobileFilter === 'detected', function ($query): void {
|
||||
$query->whereHas('phoneNumbers', function ($query): void {
|
||||
$query->where('is_valid', true)
|
||||
->where(function ($query): void {
|
||||
$query
|
||||
->where('is_mobile', true)
|
||||
->orWhere('normalized_number', 'like', '+336%')
|
||||
->orWhere('normalized_number', 'like', '+337%');
|
||||
});
|
||||
});
|
||||
})
|
||||
->when($selectedMobileFilter === 'non_mobile', function ($query): void {
|
||||
$query->whereHas('phoneNumbers', function ($query): void {
|
||||
$query->where('is_valid', true)
|
||||
->where('is_mobile', false)
|
||||
->where('normalized_number', 'not like', '+336%')
|
||||
->where('normalized_number', 'not like', '+337%');
|
||||
});
|
||||
})
|
||||
->when($selectedMobileFilter === 'invalid', function ($query): void {
|
||||
$query->whereHas('phoneNumbers', fn ($query) => $query->where('is_valid', false));
|
||||
})
|
||||
->when($selectedMobileFilter === 'missing', function ($query): void {
|
||||
$query->whereDoesntHave('phoneNumbers');
|
||||
})
|
||||
->orderByDesc('is_favorite')
|
||||
->orderBy('last_name')
|
||||
->orderBy('first_name')
|
||||
->paginate(15)
|
||||
->withQueryString();
|
||||
|
||||
$groups = ContactGroup::query()
|
||||
->withCount('contacts')
|
||||
->orderBy('name')
|
||||
->get();
|
||||
|
||||
return view('contacts.index', [
|
||||
'contacts' => $contacts,
|
||||
'groups' => $groups,
|
||||
'search' => $search,
|
||||
'selectedGroupId' => $groupId,
|
||||
'favoriteOnly' => $favoriteOnly,
|
||||
'selectedStatus' => $selectedStatus,
|
||||
'selectedMobileFilter' => $selectedMobileFilter,
|
||||
'cardDavSyncEnabled' => (bool) config('relais_sms.default_settings.carddav_sync_enabled'),
|
||||
'lastCardDavSyncRun' => SourceSyncRun::query()
|
||||
->where('source', 'carddav')
|
||||
->latest('started_at')
|
||||
->first(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): View
|
||||
{
|
||||
return view('contacts.create', [
|
||||
'groups' => ContactGroup::query()->where('source', 'local')->orderBy('name')->get(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(Request $request, PhoneNumberNormalizerInterface $normalizer): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate($this->rules());
|
||||
|
||||
$contact = DB::transaction(function () use ($request, $validated, $normalizer): Contact {
|
||||
$contact = Contact::query()->create([
|
||||
'source' => 'local',
|
||||
'first_name' => $validated['first_name'] ?? null,
|
||||
'last_name' => $validated['last_name'],
|
||||
'organization' => $validated['organization'] ?? null,
|
||||
'email' => $validated['email'] ?? null,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'sms_opt_out' => $request->boolean('sms_opt_out'),
|
||||
'is_favorite' => $request->boolean('is_favorite'),
|
||||
]);
|
||||
|
||||
$contact->groups()->sync($validated['group_ids'] ?? []);
|
||||
$this->syncPrimaryPhoneNumber($contact, $validated, $normalizer);
|
||||
|
||||
return $contact;
|
||||
});
|
||||
|
||||
return redirect()
|
||||
->route('contacts.show', $contact)
|
||||
->with('status', 'Contact cree.');
|
||||
}
|
||||
|
||||
public function show(Contact $contact): View
|
||||
{
|
||||
$contact->load(['groups', 'phoneNumbers']);
|
||||
|
||||
return view('contacts.show', [
|
||||
'contact' => $contact,
|
||||
]);
|
||||
}
|
||||
|
||||
public function favorite(Request $request, Contact $contact): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'is_favorite' => ['required', 'boolean'],
|
||||
]);
|
||||
|
||||
$contact->update([
|
||||
'is_favorite' => (bool) $validated['is_favorite'],
|
||||
]);
|
||||
|
||||
return back()->with('status', $contact->is_favorite ? 'Contact ajoute aux favoris.' : 'Contact retire des favoris.');
|
||||
}
|
||||
|
||||
public function optOut(Request $request, Contact $contact): RedirectResponse
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'sms_opt_out' => ['required', 'boolean'],
|
||||
]);
|
||||
|
||||
$contact->update([
|
||||
'sms_opt_out' => (bool) $validated['sms_opt_out'],
|
||||
]);
|
||||
|
||||
return back()->with('status', $contact->sms_opt_out ? 'Contact ajoute aux refus SMS.' : 'Contact retire des refus SMS.');
|
||||
}
|
||||
|
||||
public function edit(Contact $contact): View
|
||||
{
|
||||
$this->ensureLocalContact($contact);
|
||||
|
||||
$contact->load(['groups', 'phoneNumbers']);
|
||||
|
||||
return view('contacts.edit', [
|
||||
'contact' => $contact,
|
||||
'groups' => ContactGroup::query()->where('source', 'local')->orderBy('name')->get(),
|
||||
'primaryPhone' => $contact->phoneNumbers->firstWhere('is_primary', true) ?? $contact->phoneNumbers->first(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(Request $request, Contact $contact, PhoneNumberNormalizerInterface $normalizer): RedirectResponse
|
||||
{
|
||||
$this->ensureLocalContact($contact);
|
||||
|
||||
$validated = $request->validate($this->rules());
|
||||
|
||||
DB::transaction(function () use ($request, $contact, $validated, $normalizer): void {
|
||||
$contact->update([
|
||||
'first_name' => $validated['first_name'] ?? null,
|
||||
'last_name' => $validated['last_name'],
|
||||
'organization' => $validated['organization'] ?? null,
|
||||
'email' => $validated['email'] ?? null,
|
||||
'notes' => $validated['notes'] ?? null,
|
||||
'sms_opt_out' => $request->boolean('sms_opt_out'),
|
||||
'is_favorite' => $request->boolean('is_favorite'),
|
||||
]);
|
||||
|
||||
$contact->groups()->sync($validated['group_ids'] ?? []);
|
||||
$this->syncPrimaryPhoneNumber($contact, $validated, $normalizer);
|
||||
});
|
||||
|
||||
return redirect()
|
||||
->route('contacts.show', $contact)
|
||||
->with('status', 'Contact modifie.');
|
||||
}
|
||||
|
||||
public function destroy(Contact $contact): RedirectResponse
|
||||
{
|
||||
$this->ensureLocalContact($contact);
|
||||
|
||||
$contact->delete();
|
||||
|
||||
return redirect()
|
||||
->route('contacts.index')
|
||||
->with('status', 'Contact supprime.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<int, string>>
|
||||
*/
|
||||
private function rules(): array
|
||||
{
|
||||
return [
|
||||
'first_name' => ['nullable', 'string', 'max:255'],
|
||||
'last_name' => ['required', 'string', 'max:255'],
|
||||
'organization' => ['nullable', 'string', 'max:255'],
|
||||
'email' => ['nullable', 'email', 'max:255'],
|
||||
'notes' => ['nullable', 'string'],
|
||||
'phone_label' => ['nullable', 'string', 'max:255'],
|
||||
'raw_phone_number' => ['nullable', 'string', 'max:255'],
|
||||
'group_ids' => ['array'],
|
||||
'group_ids.*' => [
|
||||
'integer',
|
||||
Rule::exists('contact_groups', 'id')
|
||||
->where('source', 'local')
|
||||
->whereNull('deleted_at'),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
private function ensureLocalContact(Contact $contact): void
|
||||
{
|
||||
abort_if($contact->source !== 'local', 403, 'Les contacts importes ne sont pas modifiables localement.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
private function syncPrimaryPhoneNumber(Contact $contact, array $validated, PhoneNumberNormalizerInterface $normalizer): void
|
||||
{
|
||||
$rawPhoneNumber = trim((string) ($validated['raw_phone_number'] ?? ''));
|
||||
$primaryPhone = $contact->phoneNumbers()->where('is_primary', true)->first()
|
||||
?? $contact->phoneNumbers()->first();
|
||||
|
||||
if ($rawPhoneNumber === '') {
|
||||
$primaryPhone?->delete();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$result = $normalizer->normalize(
|
||||
$rawPhoneNumber,
|
||||
(string) config('relais_sms.default_settings.default_country'),
|
||||
);
|
||||
|
||||
$attributes = [
|
||||
'label' => $validated['phone_label'] ?? null,
|
||||
'raw_number' => $result->rawNumber,
|
||||
'normalized_number' => $result->normalizedNumber,
|
||||
'country' => $result->country,
|
||||
'is_valid' => $result->isValid,
|
||||
'is_mobile' => $result->isMobile,
|
||||
'is_primary' => true,
|
||||
'validation_error' => $result->error,
|
||||
];
|
||||
|
||||
if ($primaryPhone) {
|
||||
$primaryPhone->update($attributes);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$contact->phoneNumbers()->create($attributes);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user