diff --git a/app/Domains/Contact/Dav/Exporter.php b/app/Domains/Contact/Dav/Exporter.php index c4a379f39a9..29f302d5ff7 100644 --- a/app/Domains/Contact/Dav/Exporter.php +++ b/app/Domains/Contact/Dav/Exporter.php @@ -10,4 +10,12 @@ protected function escape(mixed $value): ?string return ! empty($value) ? trim($value) : null; } + + /** + * Formats and returns a string for DAV Card/Cal. + */ + protected function formatValue(?string $value): ?string + { + return ! empty($value) ? str_replace('\;', ';', trim($value)) : null; + } } diff --git a/app/Domains/Contact/Dav/Services/ImportVCard.php b/app/Domains/Contact/Dav/Services/ImportVCard.php index b1eb19a590f..e18eefea38b 100644 --- a/app/Domains/Contact/Dav/Services/ImportVCard.php +++ b/app/Domains/Contact/Dav/Services/ImportVCard.php @@ -178,7 +178,7 @@ private function processEntry(VCard $entry, string $vcard): array ]; } - return $this->processEntryContact($entry, $vcard); + return $this->processEntryCard($entry, $vcard); } /** @@ -186,7 +186,7 @@ private function processEntry(VCard $entry, string $vcard): array * * @return array */ - private function processEntryContact(VCard $entry, string $vcard): array + private function processEntryCard(VCard $entry, string $vcard): array { $result = $this->importEntry($entry); diff --git a/app/Domains/Contact/ManageContact/Dav/ImportContact.php b/app/Domains/Contact/ManageContact/Dav/ImportContact.php index 636587e417d..cda40c2494d 100644 --- a/app/Domains/Contact/ManageContact/Dav/ImportContact.php +++ b/app/Domains/Contact/ManageContact/Dav/ImportContact.php @@ -68,9 +68,9 @@ public function import(VCard $vcard, ?VCardResource $result): ?VCardResource if ($this->context->external) { $contact->distant_etag = Arr::get($this->context->data, 'etag'); $contact->distant_uri = $uri; - } - $contact->save(); + $contact->save(); + } return $contact; }); @@ -114,22 +114,17 @@ private function getExistingContact(VCard $vcard): ?Contact */ private function getContactData(?Contact $contact): array { - $result = [ + return [ 'account_id' => $this->account()->id, 'vault_id' => $this->vault()->id, 'author_id' => $this->author()->id, - 'first_name' => $contact ? $contact->first_name : null, - 'last_name' => $contact ? $contact->last_name : null, - 'middle_name' => $contact ? $contact->middle_name : null, - 'nickname' => $contact ? $contact->nickname : null, + 'contact_id' => optional($contact)->id, + 'first_name' => optional($contact)->first_name, + 'last_name' => optional($contact)->last_name, + 'middle_name' => optional($contact)->middle_name, + 'nickname' => optional($contact)->nickname, 'gender_id' => $contact ? $contact->gender_id : $this->getGender('O')->id, ]; - - if ($contact) { - $result['contact_id'] = $contact->id; - } - - return $result; } /** diff --git a/app/Domains/Contact/ManageGroups/Dav/ExportKind.php b/app/Domains/Contact/ManageGroups/Dav/ExportKind.php new file mode 100644 index 00000000000..686fe1b4158 --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ExportKind.php @@ -0,0 +1,39 @@ + + */ +#[Order(1)] +class ExportKind extends Exporter implements ExportVCardResource +{ + public function getType(): string + { + return Group::class; + } + + /** + * @param Group $resource + */ + public function export($resource, VCard $vcard): void + { + $kind = collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first(); + + if ($kind) { + $vcard->remove($kind); + $vcard->add('X-ADDRESSBOOKSERVER-KIND', 'group'); + + return; + } + + $vcard->remove('KIND'); + $vcard->add('KIND', 'group'); + } +} diff --git a/app/Domains/Contact/ManageGroups/Dav/ExportMembers.php b/app/Domains/Contact/ManageGroups/Dav/ExportMembers.php new file mode 100644 index 00000000000..8117ff5d5cd --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ExportMembers.php @@ -0,0 +1,57 @@ + + */ +#[Order(20)] +class ExportMembers extends Exporter implements ExportVCardResource +{ + public function getType(): string + { + return Group::class; + } + + /** + * @param Group $resource + */ + public function export($resource, VCard $vcard): void + { + $kind = collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first(); + + $this->exportType($resource, $vcard, $kind ? 'X-ADDRESSBOOKSERVER-MEMBER' : 'MEMBER'); + } + + private function exportType($resource, VCard $vcard, string $type): void + { + $contacts = $resource->contacts + ->map(fn (Contact $contact): string => $contact->distant_uuid ?? $contact->id) + ->sort(); + + $current = collect($vcard->select($type)); + $members = $current + ->map(fn ($member): string => $this->formatValue((string) $member)); + + // Add new members + foreach ($contacts as $contact) { + if (! $members->contains($contact)) { + $vcard->add($type, $contact); + } + } + + // Remove old members + foreach ($current as $member) { + if (! $contacts->contains($this->formatValue((string) $member))) { + $vcard->remove($member); + } + } + } +} diff --git a/app/Domains/Contact/ManageGroups/Dav/ExportNames.php b/app/Domains/Contact/ManageGroups/Dav/ExportNames.php new file mode 100644 index 00000000000..8e67f397e8e --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ExportNames.php @@ -0,0 +1,36 @@ + + */ +#[Order(10)] +class ExportNames extends Exporter implements ExportVCardResource +{ + public function getType(): string + { + return Group::class; + } + + /** + * @param Group $resource + */ + public function export($resource, VCard $vcard): void + { + $vcard->remove('FN'); + $vcard->remove('N'); + + $vcard->add('FN', $this->escape($resource->name)); + + $vcard->add('N', [ + $this->escape($resource->name), + ]); + } +} diff --git a/app/Domains/Contact/ManageGroups/Dav/ExportTimestamp.php b/app/Domains/Contact/ManageGroups/Dav/ExportTimestamp.php new file mode 100644 index 00000000000..403b8e052eb --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ExportTimestamp.php @@ -0,0 +1,30 @@ + + */ +#[Order(1000)] +class ExportTimestamp implements ExportVCardResource +{ + public function getType(): string + { + return Group::class; + } + + /** + * @param Group $resource + */ + public function export($resource, VCard $vcard): void + { + $vcard->remove('REV'); + + $vcard->REV = $resource->updated_at->format('Ymd\\THis\\Z'); + } +} diff --git a/app/Domains/Contact/ManageGroups/Dav/ImportGroup.php b/app/Domains/Contact/ManageGroups/Dav/ImportGroup.php new file mode 100644 index 00000000000..27a47b491f1 --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ImportGroup.php @@ -0,0 +1,150 @@ +KIND; + if ($kind == null) { + $kind = (string) collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first(); + } + + return $kind === 'group'; + } + + /** + * Import group. + */ + public function import(VCard $vcard, ?VCardResource $result): ?VCardResource + { + $group = $this->getExistingGroup($vcard); + + $data = $this->getGroupData($group); + $original = $data; + + $data = $this->importUid($data, $vcard); + $data = $this->importNames($data, $vcard); + + if ($group === null) { + $group = app(CreateGroup::class)->execute($data); + } elseif ($data !== $original) { + $group = app(UpdateGroup::class)->execute($data); + } + + if ($this->context->external && $group->distant_uuid === null) { + $group->distant_uuid = $this->getUid($vcard); + $group->save(); + } + + return Group::withoutTimestamps(function () use ($group): Group { + $uri = Arr::get($this->context->data, 'uri'); + if ($this->context->external) { + $group->distant_etag = Arr::get($this->context->data, 'etag'); + $group->distant_uri = $uri; + + $group->save(); + } + + return $group; + }); + } + + /** + * Get existing group. + */ + protected function getExistingGroup(VCard $vcard): ?Group + { + $group = null; + + if (($uri = Arr::get($this->context->data, 'uri')) !== null) { + $group = Group::firstWhere([ + 'vault_id' => $this->vault()->id, + 'distant_uri' => $uri, + ]); + } + + if ($group === null && ($groupId = $this->getUid($vcard)) !== null) { + $group = Group::firstWhere([ + 'vault_id' => $this->vault()->id, + 'distant_uuid' => $groupId, + ]); + } + + if ($group !== null && $group->vault_id !== $this->vault()->id) { + throw new ModelNotFoundException(); + } + + return $group; + } + + /** + * Get group data. + */ + private function getGroupData(?Group $group): array + { + return [ + 'account_id' => $this->account()->id, + 'vault_id' => $this->vault()->id, + 'author_id' => $this->author()->id, + 'group_id' => optional($group)->id, + 'name' => optional($group)->name, + ]; + } + + /** + * Import names of the group. + */ + public function importNames(array $contactData, VCard $entry): array + { + if ($this->hasNameInN($entry)) { + $contactData = $this->importNameFromN($contactData, $entry); + } elseif ($this->hasFN($entry)) { + $contactData = $this->importNameFromFN($contactData, $entry); + } else { + throw new \LogicException('Check if you can import entry!'); + } + + return $contactData; + } + + private function hasNameInN(VCard $entry): bool + { + return $entry->N !== null && ! empty(Arr::get($entry->N->getParts(), '0')); + } + + private function hasFN(VCard $entry): bool + { + return ! empty((string) $entry->FN); + } + + private function importNameFromN(array $contactData, VCard $entry): array + { + $contactData['name'] = $this->formatValue(Arr::get($entry->N->getParts(), '0')); + + return $contactData; + } + + private function importNameFromFN(array $contactData, VCard $entry): array + { + $contactData['name'] = (string) $entry->FN; + + return $contactData; + } +} diff --git a/app/Domains/Contact/ManageGroups/Dav/ImportMembers.php b/app/Domains/Contact/ManageGroups/Dav/ImportMembers.php new file mode 100644 index 00000000000..603167f6957 --- /dev/null +++ b/app/Domains/Contact/ManageGroups/Dav/ImportMembers.php @@ -0,0 +1,148 @@ +KIND; + if ($kind == null) { + $kind = (string) collect($vcard->select('X-ADDRESSBOOKSERVER-KIND'))->first(); + } + + return $kind === 'group'; + } + + /** + * Import group. + */ + public function import(VCard $vcard, ?VCardResource $result): ?VCardResource + { + $group = $this->getExistingGroup($vcard); + + if ($group !== null) { + $members = $this->importMembers($vcard); + + $this->updateGroupMembers($group, $members); + } + + return $group; + } + + /** + * Get existing group. + */ + protected function getExistingGroup(VCard $vcard): ?Group + { + $group = null; + + if (($uri = Arr::get($this->context->data, 'uri')) !== null) { + $group = Group::firstWhere([ + 'vault_id' => $this->vault()->id, + 'distant_uri' => $uri, + ]); + } + + if ($group === null && ($groupId = $this->getUid($vcard)) !== null) { + $group = Group::firstWhere([ + 'vault_id' => $this->vault()->id, + 'distant_uuid' => $groupId, + ]); + } + + if ($group !== null && $group->vault_id !== $this->vault()->id) { + throw new ModelNotFoundException(); + } + + return $group; + } + + /** + * Import members of the group. + * + * @return Collection + */ + public function importMembers(VCard $entry): Collection + { + $members = $entry->MEMBER; + + if ($members === null) { + $members = $entry->select('X-ADDRESSBOOKSERVER-MEMBER'); + } + + if ($members === null) { + return collect(); + } + + return collect($members) + ->map(fn ($member): string => $this->formatValue((string) $member)); + } + + /** + * Update group members. + * + * @param Collection $members + */ + private function updateGroupMembers(Group $group, Collection $members): void + { + // Contacts to remove + $contacts = $group->contacts + ->groupBy(fn (Contact $contact): string => $members->contains($contact->distant_uuid) || $members->contains($contact->id) + ? 'keep' + : 'remove' + ); + + $contacts->get('remove', collect()) + ->each(fn (Contact $contact) => RemoveContactFromGroup::dispatch([ + 'account_id' => $this->account()->id, + 'vault_id' => $this->vault()->id, + 'author_id' => $this->author()->id, + 'group_id' => $group->id, + 'contact_id' => $contact->id, + ])->onQueue('high')); + + // Contacts to add + $members->filter(fn (string $member): bool => ! $contacts->get('keep', collect())->contains('distant_uuid', $member) + || ! $contacts->get('keep', collect())->contains('id', $member)) + ->map(function (string $member): ?string { + $contact = Contact::firstWhere('distant_uuid', $member); + + if ($contact === null) { + $contact = Contact::find($member); + } + if ($contact === null) { + // Contact not found ! + return null; + } + + return $contact->id; + }) + ->filter() + ->each(fn (string $contactId) => AddContactToGroup::dispatch([ + 'account_id' => $this->account()->id, + 'vault_id' => $this->vault()->id, + 'author_id' => $this->author()->id, + 'group_id' => $group->id, + 'contact_id' => $contactId, + ])->onQueue('default') + ); + } +} diff --git a/app/Domains/Contact/ManageGroups/Services/AddContactToGroup.php b/app/Domains/Contact/ManageGroups/Services/AddContactToGroup.php index ff97332d419..f46fe1891bc 100644 --- a/app/Domains/Contact/ManageGroups/Services/AddContactToGroup.php +++ b/app/Domains/Contact/ManageGroups/Services/AddContactToGroup.php @@ -4,14 +4,17 @@ use App\Interfaces\ServiceInterface; use App\Models\ContactFeedItem; -use App\Models\Group; use App\Models\GroupTypeRole; -use App\Services\BaseService; +use App\Services\QueuableService; use Carbon\Carbon; +use Illuminate\Support\Arr; -class AddContactToGroup extends BaseService implements ServiceInterface +class AddContactToGroup extends QueuableService implements ServiceInterface { - private array $data; + /** + * The optional group type role. + */ + private ?GroupTypeRole $role = null; /** * Get the validation rules that apply to the service. @@ -45,38 +48,30 @@ public function permissions(): array /** * Add a contact to a group. */ - public function execute(array $data): Group + public function execute(array $data): void { $this->data = $data; $this->validate(); - if ($this->data['group_type_role_id'] != 0) { - $this->group->contacts()->syncWithoutDetaching([ - $this->contact->id => ['group_type_role_id' => $this->data['group_type_role_id']], - ]); - } else { - $this->group->contacts()->syncWithoutDetaching([ - $this->contact->id => ['group_type_role_id' => null], - ]); - } + $this->group->contacts()->syncWithoutDetaching([ + $this->contact->id => ['group_type_role_id' => optional($this->role)->id], + ]); $this->group->touch(); $this->updateLastEditedDate(); $this->createFeedItem(); - - return $this->group; } private function validate(): void { $this->validateRules($this->data); - if ($this->data['group_type_role_id'] != 0) { - $role = GroupTypeRole::findOrFail($this->data['group_type_role_id']); + if (($groupTypeRoleId = Arr::get($this->data, 'group_type_role_id', 0)) != 0) { + $this->role = GroupTypeRole::findOrFail($groupTypeRoleId); $this->account()->groupTypes() - ->findOrFail($role->group_type_id); + ->findOrFail($this->role->group_type_id); } } diff --git a/app/Domains/Contact/ManageGroups/Services/CreateGroup.php b/app/Domains/Contact/ManageGroups/Services/CreateGroup.php index 5cc23cc2715..7d31308eca4 100644 --- a/app/Domains/Contact/ManageGroups/Services/CreateGroup.php +++ b/app/Domains/Contact/ManageGroups/Services/CreateGroup.php @@ -5,6 +5,7 @@ use App\Interfaces\ServiceInterface; use App\Models\Group; use App\Services\BaseService; +use Illuminate\Support\Arr; class CreateGroup extends BaseService implements ServiceInterface { @@ -19,7 +20,7 @@ public function rules(): array 'account_id' => 'required|uuid|exists:accounts,id', 'vault_id' => 'required|uuid|exists:vaults,id', 'author_id' => 'required|uuid|exists:users,id', - 'group_type_id' => 'required|integer|exists:group_types,id', + 'group_type_id' => 'nullable|integer|exists:group_types,id', 'name' => 'nullable|string|max:255', ]; } @@ -45,7 +46,7 @@ public function execute(array $data): Group $this->validate(); $this->group = Group::create([ - 'group_type_id' => $data['group_type_id'], + 'group_type_id' => Arr::get($data, 'group_type_id'), 'vault_id' => $data['vault_id'], 'name' => $this->valueOrNull($data, 'name'), ]); @@ -57,7 +58,9 @@ private function validate(): void { $this->validateRules($this->data); - $this->account()->groupTypes() - ->findOrFail($this->data['group_type_id']); + if (($groupTypeId = Arr::get($this->data, 'group_type_id')) !== null) { + $this->account()->groupTypes() + ->findOrFail($groupTypeId); + } } } diff --git a/app/Domains/Contact/ManageGroups/Services/DestroyGroup.php b/app/Domains/Contact/ManageGroups/Services/DestroyGroup.php index c6c3e6af2a0..9218a33c7dc 100644 --- a/app/Domains/Contact/ManageGroups/Services/DestroyGroup.php +++ b/app/Domains/Contact/ManageGroups/Services/DestroyGroup.php @@ -3,9 +3,9 @@ namespace App\Domains\Contact\ManageGroups\Services; use App\Interfaces\ServiceInterface; -use App\Services\BaseService; +use App\Services\QueuableService; -class DestroyGroup extends BaseService implements ServiceInterface +class DestroyGroup extends QueuableService implements ServiceInterface { /** * Get the validation rules that apply to the service. diff --git a/app/Domains/Contact/ManageGroups/Services/RemoveContactFromGroup.php b/app/Domains/Contact/ManageGroups/Services/RemoveContactFromGroup.php index 9baa2b805dc..942133f637b 100644 --- a/app/Domains/Contact/ManageGroups/Services/RemoveContactFromGroup.php +++ b/app/Domains/Contact/ManageGroups/Services/RemoveContactFromGroup.php @@ -4,11 +4,10 @@ use App\Interfaces\ServiceInterface; use App\Models\ContactFeedItem; -use App\Models\Group; -use App\Services\BaseService; +use App\Services\QueuableService; use Carbon\Carbon; -class RemoveContactFromGroup extends BaseService implements ServiceInterface +class RemoveContactFromGroup extends QueuableService implements ServiceInterface { /** * Get the validation rules that apply to the service. @@ -41,18 +40,17 @@ public function permissions(): array /** * Remove a contact from a group. */ - public function execute(array $data): Group + public function execute(array $data): void { $this->validateRules($data); $this->group->contacts()->detach([ $this->contact->id, ]); + $this->group->touch(); $this->updateLastEditedDate(); $this->createFeedItem(); - - return $this->group; } private function updateLastEditedDate(): void diff --git a/app/Domains/Contact/ManageGroups/Services/UpdateGroup.php b/app/Domains/Contact/ManageGroups/Services/UpdateGroup.php index 84ec48a239f..9dc3b8d864f 100644 --- a/app/Domains/Contact/ManageGroups/Services/UpdateGroup.php +++ b/app/Domains/Contact/ManageGroups/Services/UpdateGroup.php @@ -5,6 +5,7 @@ use App\Interfaces\ServiceInterface; use App\Models\Group; use App\Services\BaseService; +use Illuminate\Support\Arr; class UpdateGroup extends BaseService implements ServiceInterface { @@ -20,7 +21,7 @@ public function rules(): array 'vault_id' => 'required|uuid|exists:vaults,id', 'author_id' => 'required|uuid|exists:users,id', 'group_id' => 'required|integer|exists:groups,id', - 'group_type_id' => 'required|integer|exists:group_types,id', + 'group_type_id' => 'nullable|integer|exists:group_types,id', 'name' => 'nullable|string|max:255', ]; } @@ -57,7 +58,9 @@ private function validate(): void { $this->validateRules($this->data); - $this->account()->groupTypes() - ->findOrFail($this->data['group_type_id']); + if (($groupTypeId = Arr::get($this->data, 'group_type_id')) !== null) { + $this->account()->groupTypes() + ->findOrFail($groupTypeId); + } } } diff --git a/app/Domains/Contact/ManageGroups/Web/Controllers/ContactModuleGroupController.php b/app/Domains/Contact/ManageGroups/Web/Controllers/ContactModuleGroupController.php index 1d1e0d662ca..0b6f3dbf2e9 100644 --- a/app/Domains/Contact/ManageGroups/Web/Controllers/ContactModuleGroupController.php +++ b/app/Domains/Contact/ManageGroups/Web/Controllers/ContactModuleGroupController.php @@ -8,6 +8,7 @@ use App\Domains\Contact\ManageGroups\Web\ViewHelpers\ModuleGroupsViewHelper; use App\Http\Controllers\Controller; use App\Models\Contact; +use App\Models\Group; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -50,9 +51,10 @@ public function store(Request $request, string $vaultId, string $contactId): Jso 'group_type_role_id' => $request->input('group_type_role_id') ?? null, ]; - $group = (new AddContactToGroup())->execute($data); + AddContactToGroup::dispatchSync($data); $contact = Contact::find($contactId); + $group = Group::find($data['group_id']); return response()->json([ 'data' => ModuleGroupsViewHelper::dto($contact, $group, true), @@ -69,8 +71,9 @@ public function destroy(Request $request, string $vaultId, string $contactId, in 'group_id' => $groupId, ]; - $group = (new RemoveContactFromGroup())->execute($data); + RemoveContactFromGroup::dispatch($data)->onQueue('high'); $contact = Contact::find($contactId); + $group = Group::find($groupId); return response()->json([ 'data' => ModuleGroupsViewHelper::dto($contact, $group, false), diff --git a/app/Domains/Contact/ManageGroups/Web/ViewHelpers/GroupShowViewHelper.php b/app/Domains/Contact/ManageGroups/Web/ViewHelpers/GroupShowViewHelper.php index 251c3f9df64..51123bb8c6d 100644 --- a/app/Domains/Contact/ManageGroups/Web/ViewHelpers/GroupShowViewHelper.php +++ b/app/Domains/Contact/ManageGroups/Web/ViewHelpers/GroupShowViewHelper.php @@ -17,30 +17,32 @@ class GroupShowViewHelper */ public static function data(Group $group): array { - $rolesCollection = $group->groupType->groupTypeRoles() - ->orderBy('position') - ->get() - ->map(function (GroupTypeRole $role) use ($group) { - $contactsCollection = $group->contacts() - ->wherePivot('group_type_role_id', $role->id) - ->get() - ->map(fn (Contact $contact) => [ - 'id' => $contact->id, - 'name' => $contact->name, - 'age' => $contact->age, - 'avatar' => $contact->avatar, - 'url' => route('contact.show', [ - 'vault' => $contact->vault_id, - 'contact' => $contact->id, - ]), - ]); + $rolesCollection = $group->groupType === null + ? collect() + : $group->groupType->groupTypeRoles() + ->orderBy('position') + ->get() + ->map(function (GroupTypeRole $role) use ($group) { + $contactsCollection = $group->contacts() + ->wherePivot('group_type_role_id', $role->id) + ->get() + ->map(fn (Contact $contact) => [ + 'id' => $contact->id, + 'name' => $contact->name, + 'age' => $contact->age, + 'avatar' => $contact->avatar, + 'url' => route('contact.show', [ + 'vault' => $contact->vault_id, + 'contact' => $contact->id, + ]), + ]); - return [ - 'id' => $role->id, - 'label' => $role->label, - 'contacts' => $contactsCollection, - ]; - }); + return [ + 'id' => $role->id, + 'label' => $role->label, + 'contacts' => $contactsCollection, + ]; + }); // now we get all the contacts that are not assigned to a role $contactsCollection = $group->contacts() @@ -61,7 +63,7 @@ public static function data(Group $group): array if ($contactsCollection->isNotEmpty()) { $rolesCollection->push([ 'id' => -1, - 'label' => 'No role', + 'label' => trans('No role'), 'contacts' => $contactsCollection, ]); } @@ -71,7 +73,7 @@ public static function data(Group $group): array 'name' => $group->name, 'contact_count' => $group->contacts->count(), 'type' => [ - 'label' => $group->groupType->label, + 'label' => optional($group->groupType)->label, ], 'roles' => $rolesCollection, 'url' => [ diff --git a/app/Domains/Contact/ManageGroups/Web/ViewHelpers/ModuleGroupsViewHelper.php b/app/Domains/Contact/ManageGroups/Web/ViewHelpers/ModuleGroupsViewHelper.php index 04435b58c98..42ad74b818e 100644 --- a/app/Domains/Contact/ManageGroups/Web/ViewHelpers/ModuleGroupsViewHelper.php +++ b/app/Domains/Contact/ManageGroups/Web/ViewHelpers/ModuleGroupsViewHelper.php @@ -71,22 +71,24 @@ public static function dto(Contact $contact, Group $group, bool $taken = false): ]; }); - $roles = $group->groupType - ->groupTypeRoles() - ->orderBy('position') - ->get() - ->map(function ($role) { - return [ - 'id' => $role->id, - 'label' => $role->label, - ]; - }); + $roles = $group->groupType === null + ? collect() + : $group->groupType + ->groupTypeRoles() + ->orderBy('position') + ->get() + ->map(function ($role) { + return [ + 'id' => $role->id, + 'label' => $role->label, + ]; + }); return [ 'id' => $group->id, 'name' => $group->name, 'type' => [ - 'id' => $group->groupType->id, + 'id' => optional($group->groupType)->id, ], 'contacts' => $contacts, 'roles' => $roles, diff --git a/database/migrations/2021_10_09_204235_create_group_table.php b/database/migrations/2021_10_09_204235_create_group_table.php index cb93753e3db..d36a6f656e2 100644 --- a/database/migrations/2021_10_09_204235_create_group_table.php +++ b/database/migrations/2021_10_09_204235_create_group_table.php @@ -42,7 +42,7 @@ public function up() $table->id(); $table->uuid('uuid')->nullable(); $table->foreignIdFor(Vault::class)->constrained()->cascadeOnDelete(); - $table->foreignIdFor(GroupType::class)->constrained()->cascadeOnDelete(); + $table->foreignIdFor(GroupType::class)->nullable()->constrained()->nullOnDelete(); $table->string('name'); $table->mediumText('vcard')->nullable(); diff --git a/database/migrations/2023_08_22_175035_fix_group_grouptype.php b/database/migrations/2023_08_22_175035_fix_group_grouptype.php new file mode 100644 index 00000000000..aabd3dc0d37 --- /dev/null +++ b/database/migrations/2023_08_22_175035_fix_group_grouptype.php @@ -0,0 +1,26 @@ +unsignedBigInteger('group_type_id')->nullable()->change(); + + if (DB::connection()->getDriverName() !== 'sqlite') { + $table->dropForeign('groups_group_type_id_foreign'); + $table->foreign('group_type_id')->references('id')->on('group_types')->nullOnDelete(); + } + }); + } +}; diff --git a/lang/bn.json b/lang/bn.json index 410a8d194c1..b8f14aa02d5 100644 --- a/lang/bn.json +++ b/lang/bn.json @@ -639,8 +639,8 @@ "New Password": "নতুন পাসওয়ার্ড", "New to Monica?": "মনিকা নতুন?", "Next": "পরবর্তী", - "nickname": "ডাকনাম", "Nickname": "ডাকনাম", + "nickname": "ডাকনাম", "No cities have been added yet in any contact’s addresses.": "কোনো পরিচিতির ঠিকানায় এখনো কোনো শহর যোগ করা হয়নি।", "No contacts found.": "কোন পরিচিতি পাওয়া যায়নি.", "No countries have been added yet in any contact’s addresses.": "কোনো পরিচিতির ঠিকানায় এখনো কোনো দেশ যোগ করা হয়নি।", @@ -652,6 +652,7 @@ "No life event types yet.": "এখনও কোন জীবন ঘটনা প্রকার.", "No notes found.": "কোন নোট পাওয়া যায়নি.", "No results found": "কোন ফলাফল পাওয়া যায়নি", + "No role": "কোন ভূমিকা নেই", "No roles yet.": "এখনও কোন ভূমিকা নেই.", "No tasks.": "কোনো কাজ নেই।", "Notes": "মন্তব্য", diff --git a/lang/ca.json b/lang/ca.json index d1ac6f608bb..6dbe212ecfa 100644 --- a/lang/ca.json +++ b/lang/ca.json @@ -639,8 +639,8 @@ "New Password": "nova contrasenya", "New to Monica?": "Nou a la Mònica?", "Next": "Pròxim", - "nickname": "sobrenom", "Nickname": "Pseudònim", + "nickname": "sobrenom", "No cities have been added yet in any contact’s addresses.": "Encara no s'ha afegit cap ciutat a les adreces de cap contacte.", "No contacts found.": "No s'han trobat contactes.", "No countries have been added yet in any contact’s addresses.": "Encara no s'ha afegit cap país a les adreces de cap contacte.", @@ -652,6 +652,7 @@ "No life event types yet.": "Encara no hi ha tipus d'esdeveniments vitals.", "No notes found.": "No s'han trobat notes.", "No results found": "Sense resultats", + "No role": "Cap paper", "No roles yet.": "Encara no hi ha cap paper.", "No tasks.": "Sense tasques.", "Notes": "Notes", diff --git a/lang/da.json b/lang/da.json index 072587a42fb..329c8fbb3b7 100644 --- a/lang/da.json +++ b/lang/da.json @@ -639,8 +639,8 @@ "New Password": "nyt kodeord", "New to Monica?": "Ny for Monica?", "Next": "Næste", - "nickname": "kaldenavn", "Nickname": "Kaldenavn", + "nickname": "kaldenavn", "No cities have been added yet in any contact’s addresses.": "Ingen byer er blevet tilføjet endnu i nogen kontakts adresser.", "No contacts found.": "Ingen kontakter fundet.", "No countries have been added yet in any contact’s addresses.": "Ingen lande er blevet tilføjet endnu i nogen kontakts adresser.", @@ -652,6 +652,7 @@ "No life event types yet.": "Ingen livsbegivenhedstyper endnu.", "No notes found.": "Ingen noter fundet.", "No results found": "Ingen resultater fundet", + "No role": "Ingen rolle", "No roles yet.": "Ingen roller endnu.", "No tasks.": "Ingen opgaver.", "Notes": "Noter", diff --git a/lang/de.json b/lang/de.json index 27c2105b4f6..4a84f1098cc 100644 --- a/lang/de.json +++ b/lang/de.json @@ -639,8 +639,8 @@ "New Password": "Neues Passwort", "New to Monica?": "Neu bei Monica?", "Next": "Nächster", - "nickname": "Spitzname", "Nickname": "Spitzname", + "nickname": "Spitzname", "No cities have been added yet in any contact’s addresses.": "In den Adressen Ihrer Kontakte wurden noch keine Städte hinzugefügt.", "No contacts found.": "Keine Kontakte gefunden.", "No countries have been added yet in any contact’s addresses.": "In den Adressen Ihrer Kontakte wurden noch keine Länder hinzugefügt.", @@ -652,6 +652,7 @@ "No life event types yet.": "Noch keine Lebensereignistypen vorhanden.", "No notes found.": "Keine Notizen gefunden.", "No results found": "Keine Ergebnisse gefunden", + "No role": "Keine Rolle", "No roles yet.": "Keine Rollen vorhanden.", "No tasks.": "Keine Aufgaben.", "Notes": "Notizen", diff --git a/lang/el.json b/lang/el.json index f5f45eb9d66..97437d3c5b6 100644 --- a/lang/el.json +++ b/lang/el.json @@ -639,8 +639,8 @@ "New Password": "Νέος Κωδικός", "New to Monica?": "Νέος στη Μόνικα;", "Next": "Επόμενο", - "nickname": "παρατσούκλι", "Nickname": "Παρατσούκλι", + "nickname": "παρατσούκλι", "No cities have been added yet in any contact’s addresses.": "Δεν έχουν προστεθεί ακόμη πόλεις στις διευθύνσεις οποιασδήποτε επαφής.", "No contacts found.": "Δεν βρέθηκαν επαφές.", "No countries have been added yet in any contact’s addresses.": "Δεν έχουν προστεθεί ακόμη χώρες στις διευθύνσεις οποιασδήποτε επαφής.", @@ -652,6 +652,7 @@ "No life event types yet.": "Δεν υπάρχουν ακόμη τύποι συμβάντων ζωής.", "No notes found.": "Δεν βρέθηκαν σημειώσεις.", "No results found": "Δεν βρέθηκαν αποτελέσματα", + "No role": "Κανένας ρόλος", "No roles yet.": "Δεν υπάρχουν ακόμη ρόλοι.", "No tasks.": "Χωρίς εργασίες.", "Notes": "Σημειώσεις", diff --git a/lang/es.json b/lang/es.json index 41e65873cf5..039b0a65b93 100644 --- a/lang/es.json +++ b/lang/es.json @@ -639,8 +639,8 @@ "New Password": "Nueva Contraseña", "New to Monica?": "¿Nuevo en Monica?", "Next": "Siguiente", - "nickname": "Apodo", "Nickname": "Apodo", + "nickname": "Apodo", "No cities have been added yet in any contact’s addresses.": "Todavía no se han agregado ciudades en las direcciones de ningún contacto.", "No contacts found.": "No se encontraron contactos.", "No countries have been added yet in any contact’s addresses.": "Todavía no se han agregado países en las direcciones de ningún contacto.", @@ -652,6 +652,7 @@ "No life event types yet.": "Aún no hay tipos de eventos de la vida.", "No notes found.": "No se encontraron notas.", "No results found": "No se encontraron resultados", + "No role": "Sin rol", "No roles yet.": "Todavía no hay roles.", "No tasks.": "Sin tareas.", "Notes": "Notas", diff --git a/lang/fr.json b/lang/fr.json index 063b5bd2225..b86c04f0b64 100644 --- a/lang/fr.json +++ b/lang/fr.json @@ -639,8 +639,8 @@ "New Password": "Nouveau mot de passe", "New to Monica?": "Nouveau sur Monica ?", "Next": "Suivant", - "nickname": "surnom", "Nickname": "Surnom", + "nickname": "surnom", "No cities have been added yet in any contact’s addresses.": "Aucune ville n’a encore été ajoutée dans les adresses d’un contact.", "No contacts found.": "Aucun contact trouvé.", "No countries have been added yet in any contact’s addresses.": "Aucun pays n’a encore été ajouté dans les adresses d’un contact.", @@ -652,6 +652,7 @@ "No life event types yet.": "Pas encore de types d’événements de la vie.", "No notes found.": "Aucune note trouvée.", "No results found": "Aucun résultat", + "No role": "Aucun rôle", "No roles yet.": "Pas encore de rôles.", "No tasks.": "Pas de tâches.", "Notes": "Notes", diff --git a/lang/he.json b/lang/he.json index 83548baf484..dcf2b318ddd 100644 --- a/lang/he.json +++ b/lang/he.json @@ -639,8 +639,8 @@ "New Password": "סיסמה חדשה", "New to Monica?": "חדש במוניקה?", "Next": "הַבָּא", - "nickname": "כינוי", "Nickname": "כינוי", + "nickname": "כינוי", "No cities have been added yet in any contact’s addresses.": "עדיין לא נוספו ערים בכתובות של איש קשר.", "No contacts found.": "לא נמצאו אנשי קשר.", "No countries have been added yet in any contact’s addresses.": "לא נוספו עדיין מדינות בכתובות של איש קשר.", @@ -652,6 +652,7 @@ "No life event types yet.": "עדיין אין סוגי אירועי חיים.", "No notes found.": "לא נמצאו הערות.", "No results found": "לא נמצאו תוצאות", + "No role": "אין תפקיד", "No roles yet.": "עדיין אין תפקידים.", "No tasks.": "אין משימות.", "Notes": "הערות", diff --git a/lang/hi.json b/lang/hi.json index 87bf1560524..bb6d54b15cc 100644 --- a/lang/hi.json +++ b/lang/hi.json @@ -639,8 +639,8 @@ "New Password": "नया पासवर्ड", "New to Monica?": "मोनिका के लिए नया?", "Next": "अगला", - "nickname": "उपनाम", "Nickname": "उपनाम", + "nickname": "उपनाम", "No cities have been added yet in any contact’s addresses.": "किसी भी संपर्क के पतों में अभी तक कोई शहर नहीं जोड़ा गया है।", "No contacts found.": "कोई संपर्क नहीं मिला.", "No countries have been added yet in any contact’s addresses.": "किसी भी संपर्क के पतों में अभी तक कोई देश नहीं जोड़ा गया है।", @@ -652,6 +652,7 @@ "No life event types yet.": "अभी तक कोई जीवन घटना प्रकार नहीं है।", "No notes found.": "कोई नोट नहीं मिला.", "No results found": "कोई परिणाम नहीं मिला", + "No role": "कोई भूमिका नहीं", "No roles yet.": "अभी तक कोई भूमिका नहीं है।", "No tasks.": "कोई कार्य नहीं।", "Notes": "टिप्पणियाँ", diff --git a/lang/it.json b/lang/it.json index 2a026bcc572..a7c457fb820 100644 --- a/lang/it.json +++ b/lang/it.json @@ -639,8 +639,8 @@ "New Password": "Nuova password", "New to Monica?": "Nuovo su Monica?", "Next": "Prossimo", - "nickname": "soprannome", "Nickname": "Soprannome", + "nickname": "soprannome", "No cities have been added yet in any contact’s addresses.": "Non sono state ancora aggiunte città negli indirizzi di nessun contatto.", "No contacts found.": "Nessun contatto trovato.", "No countries have been added yet in any contact’s addresses.": "Non sono ancora stati aggiunti paesi in nessun indirizzo dei contatti.", @@ -652,6 +652,7 @@ "No life event types yet.": "Non ci sono ancora tipi di eventi di vita.", "No notes found.": "Nessuna nota trovata.", "No results found": "Nessun risultato trovato", + "No role": "Nessun ruolo", "No roles yet.": "Ancora nessun ruolo.", "No tasks.": "Nessun compito.", "Notes": "Note", diff --git a/lang/ja.json b/lang/ja.json index b6f3b7d11ac..07c39d86fd8 100644 --- a/lang/ja.json +++ b/lang/ja.json @@ -639,8 +639,8 @@ "New Password": "新しいパスワード", "New to Monica?": "モニカは初めてですか?", "Next": "次", - "nickname": "ニックネーム", "Nickname": "ニックネーム", + "nickname": "ニックネーム", "No cities have been added yet in any contact’s addresses.": "連絡先の住所に都市が追加されていません。", "No contacts found.": "連絡先が見つかりません。", "No countries have been added yet in any contact’s addresses.": "どの連絡先の住所にも国が追加されていません。", @@ -652,6 +652,7 @@ "No life event types yet.": "ライフ イベント タイプはまだありません。", "No notes found.": "メモが見つかりません。", "No results found": "結果が見つかりません", + "No role": "役割なし", "No roles yet.": "ロールはまだありません。", "No tasks.": "タスクはありません。", "Notes": "ノート", diff --git a/lang/ml.json b/lang/ml.json index 86b1d0637e8..73278599d8d 100644 --- a/lang/ml.json +++ b/lang/ml.json @@ -639,8 +639,8 @@ "New Password": "പുതിയ പാസ്വേഡ്", "New to Monica?": "മോണിക്കയിൽ പുതിയത്?", "Next": "അടുത്തത്", - "nickname": "വിളിപ്പേര്", "Nickname": "വിളിപ്പേര്", + "nickname": "വിളിപ്പേര്", "No cities have been added yet in any contact’s addresses.": "ഒരു കോൺടാക്റ്റിന്റെയും വിലാസത്തിൽ ഇതുവരെ നഗരങ്ങളൊന്നും ചേർത്തിട്ടില്ല.", "No contacts found.": "കോൺടാക്‌റ്റുകളൊന്നും കണ്ടെത്തിയില്ല.", "No countries have been added yet in any contact’s addresses.": "ഒരു കോൺടാക്റ്റിന്റെയും വിലാസങ്ങളിൽ ഇതുവരെ രാജ്യങ്ങളൊന്നും ചേർത്തിട്ടില്ല.", @@ -652,6 +652,7 @@ "No life event types yet.": "ഇതുവരെ ജീവിത സംഭവങ്ങളുടെ തരങ്ങളൊന്നുമില്ല.", "No notes found.": "കുറിപ്പുകളൊന്നും കണ്ടെത്തിയില്ല.", "No results found": "ഒരു ഫലവും കണ്ടെത്താനായില്ല", + "No role": "വേഷമില്ല", "No roles yet.": "ഇതുവരെ വേഷങ്ങളൊന്നുമില്ല.", "No tasks.": "ജോലികളൊന്നുമില്ല.", "Notes": "കുറിപ്പുകൾ", diff --git a/lang/nl.json b/lang/nl.json index f30de2fa054..214c90faf60 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -639,8 +639,8 @@ "New Password": "nieuw paswoord", "New to Monica?": "Nieuw bij Monica?", "Next": "Volgende", - "nickname": "bijnaam", "Nickname": "Bijnaam", + "nickname": "bijnaam", "No cities have been added yet in any contact’s addresses.": "Er zijn nog geen steden toegevoegd aan de adressen van een contact.", "No contacts found.": "Geen contacten gevonden.", "No countries have been added yet in any contact’s addresses.": "Er zijn nog geen landen toegevoegd aan de adressen van contactpersonen.", @@ -652,6 +652,7 @@ "No life event types yet.": "Er zijn nog geen typen levensgebeurtenissen.", "No notes found.": "Geen notities gevonden.", "No results found": "geen resultaten gevonden", + "No role": "Geen rol", "No roles yet.": "Nog geen rollen.", "No tasks.": "Geen taken.", "Notes": "Notities", diff --git a/lang/no.json b/lang/no.json index 6a85ca9eb5b..984b128482c 100644 --- a/lang/no.json +++ b/lang/no.json @@ -639,8 +639,8 @@ "New Password": "Nytt passord", "New to Monica?": "Ny hos Monica?", "Next": "Neste", - "nickname": "kallenavn", "Nickname": "Kallenavn", + "nickname": "kallenavn", "No cities have been added yet in any contact’s addresses.": "Ingen byer er lagt til i noen kontakts adresser ennå.", "No contacts found.": "Ingen kontakter funnet.", "No countries have been added yet in any contact’s addresses.": "Ingen land er lagt til i noen kontakts adresser ennå.", @@ -652,6 +652,7 @@ "No life event types yet.": "Ingen livshendelsestyper ennå.", "No notes found.": "Ingen notater funnet.", "No results found": "Ingen resultater", + "No role": "Ingen rolle", "No roles yet.": "Ingen roller ennå.", "No tasks.": "Ingen oppgaver.", "Notes": "Notater", diff --git a/lang/pa.json b/lang/pa.json index 1aca3c94452..099047cbbd8 100644 --- a/lang/pa.json +++ b/lang/pa.json @@ -639,8 +639,8 @@ "New Password": "ਨਵਾਂ ਪਾਸਵਰਡ", "New to Monica?": "ਮੋਨਿਕਾ ਲਈ ਨਵੇਂ?", "Next": "ਅਗਲਾ", - "nickname": "ਉਪਨਾਮ", "Nickname": "ਉਪਨਾਮ", + "nickname": "ਉਪਨਾਮ", "No cities have been added yet in any contact’s addresses.": "ਕਿਸੇ ਵੀ ਸੰਪਰਕ ਦੇ ਪਤੇ ਵਿੱਚ ਅਜੇ ਤੱਕ ਕੋਈ ਸ਼ਹਿਰ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।", "No contacts found.": "ਕੋਈ ਸੰਪਰਕ ਨਹੀਂ ਮਿਲੇ।", "No countries have been added yet in any contact’s addresses.": "ਕਿਸੇ ਵੀ ਸੰਪਰਕ ਦੇ ਪਤੇ ਵਿੱਚ ਅਜੇ ਤੱਕ ਕੋਈ ਦੇਸ਼ ਸ਼ਾਮਲ ਨਹੀਂ ਕੀਤਾ ਗਿਆ ਹੈ।", @@ -652,6 +652,7 @@ "No life event types yet.": "ਅਜੇ ਤੱਕ ਕੋਈ ਜੀਵਨ ਘਟਨਾ ਕਿਸਮ ਨਹੀਂ ਹੈ।", "No notes found.": "ਕੋਈ ਨੋਟ ਨਹੀਂ ਮਿਲੇ।", "No results found": "ਕੋਈ ਨਤੀਜੇ ਨਹੀਂ ਮਿਲੇ", + "No role": "ਕੋਈ ਭੂਮਿਕਾ ਨਹੀਂ", "No roles yet.": "ਅਜੇ ਤੱਕ ਕੋਈ ਭੂਮਿਕਾਵਾਂ ਨਹੀਂ ਹਨ।", "No tasks.": "ਕੋਈ ਕੰਮ ਨਹੀਂ।", "Notes": "ਨੋਟਸ", diff --git a/lang/pl.json b/lang/pl.json index 00098727226..0814fa406a7 100644 --- a/lang/pl.json +++ b/lang/pl.json @@ -639,8 +639,8 @@ "New Password": "nowe hasło", "New to Monica?": "Nowy dla Moniki?", "Next": "Następny", - "nickname": "przezwisko", "Nickname": "Przezwisko", + "nickname": "przezwisko", "No cities have been added yet in any contact’s addresses.": "Żadne miasto nie zostało jeszcze dodane w adresach żadnego kontaktu.", "No contacts found.": "Nie znaleziono kontaktów.", "No countries have been added yet in any contact’s addresses.": "Żadne kraje nie zostały jeszcze dodane w adresach żadnego kontaktu.", @@ -652,6 +652,7 @@ "No life event types yet.": "Brak typów zdarzeń życiowych.", "No notes found.": "Nie znaleziono notatek.", "No results found": "Nie znaleziono wyników", + "No role": "Żadnej roli", "No roles yet.": "Nie ma jeszcze ról.", "No tasks.": "Brak zadań.", "Notes": "Notatki", diff --git a/lang/pt.json b/lang/pt.json index c92dda92e2a..f09133fcfc3 100644 --- a/lang/pt.json +++ b/lang/pt.json @@ -639,8 +639,8 @@ "New Password": "Nova Palavra-passe", "New to Monica?": "Novo na Monica?", "Next": "Próximo", - "nickname": "apelido", "Nickname": "Apelido", + "nickname": "apelido", "No cities have been added yet in any contact’s addresses.": "Nenhuma cidade foi adicionada ainda nos endereços dos contatos.", "No contacts found.": "Nenhum contato encontrado.", "No countries have been added yet in any contact’s addresses.": "Nenhum país foi adicionado ainda nos endereços dos contatos.", @@ -652,6 +652,7 @@ "No life event types yet.": "Ainda não há tipos de eventos de vida.", "No notes found.": "Nenhuma nota encontrada.", "No results found": "Nenhum resultado encontrado", + "No role": "Sem função", "No roles yet.": "Nenhum papel ainda.", "No tasks.": "Nenhuma tarefa.", "Notes": "Notas", diff --git a/lang/ro.json b/lang/ro.json index a02af1121b6..89222b1127c 100644 --- a/lang/ro.json +++ b/lang/ro.json @@ -639,8 +639,8 @@ "New Password": "Parolă Nouă", "New to Monica?": "Nou cu Monica?", "Next": "Următorul", - "nickname": "poreclă", "Nickname": "Poreclă", + "nickname": "poreclă", "No cities have been added yet in any contact’s addresses.": "Niciun oraș nu a fost adăugat încă la adresele niciunei persoane de contact.", "No contacts found.": "Nu s-au găsit persoane de contact.", "No countries have been added yet in any contact’s addresses.": "Nicio țară nu a fost adăugată încă la adresele vreunei persoane de contact.", @@ -652,6 +652,7 @@ "No life event types yet.": "Încă nu există tipuri de evenimente de viață.", "No notes found.": "Nu s-au găsit note.", "No results found": "Nici un rezultat gasit", + "No role": "Nici un rol", "No roles yet.": "Încă nu există roluri.", "No tasks.": "Fără sarcini.", "Notes": "Note", diff --git a/lang/ru.json b/lang/ru.json index bd124e28189..bc5aaab9e83 100644 --- a/lang/ru.json +++ b/lang/ru.json @@ -639,8 +639,8 @@ "New Password": "Новый пароль", "New to Monica?": "Новичок в Monica?", "Next": "Следующий", - "nickname": "прозвище", "Nickname": "Прозвище", + "nickname": "прозвище", "No cities have been added yet in any contact’s addresses.": "В адресах контактов еще не указаны города.", "No contacts found.": "Контакты не найдены.", "No countries have been added yet in any contact’s addresses.": "В адресах контакта еще не добавлены страны.", @@ -652,6 +652,7 @@ "No life event types yet.": "Типы жизненных событий еще не созданы.", "No notes found.": "Заметки не найдены.", "No results found": "Результаты не найдены.", + "No role": "Нет роли", "No roles yet.": "Роли еще не созданы.", "No tasks.": "Задач нет.", "Notes": "Заметки", diff --git a/lang/sv.json b/lang/sv.json index ef2001b56ab..d5eb33bd1ca 100644 --- a/lang/sv.json +++ b/lang/sv.json @@ -639,8 +639,8 @@ "New Password": "nytt lösenord", "New to Monica?": "Ny hos Monica?", "Next": "Nästa", - "nickname": "smeknamn", "Nickname": "Smeknamn", + "nickname": "smeknamn", "No cities have been added yet in any contact’s addresses.": "Inga städer har lagts till ännu i någon kontakts adresser.", "No contacts found.": "Inga kontakter hittades.", "No countries have been added yet in any contact’s addresses.": "Inga länder har lagts till ännu i någon kontakts adresser.", @@ -652,6 +652,7 @@ "No life event types yet.": "Inga typer av livshändelser än.", "No notes found.": "Inga anteckningar hittades.", "No results found": "Inga resultat funna", + "No role": "Ingen roll", "No roles yet.": "Inga roller än.", "No tasks.": "Inga uppgifter.", "Notes": "Anteckningar", diff --git a/lang/te.json b/lang/te.json index 6f365eb8e82..53cf4998eb0 100644 --- a/lang/te.json +++ b/lang/te.json @@ -639,8 +639,8 @@ "New Password": "కొత్త పాస్వర్డ్", "New to Monica?": "మోనికాకు కొత్త?", "Next": "తరువాత", - "nickname": "మారుపేరు", "Nickname": "మారుపేరు", + "nickname": "మారుపేరు", "No cities have been added yet in any contact’s addresses.": "ఏ సంప్రదింపు చిరునామాలలో ఇంకా నగరాలు జోడించబడలేదు.", "No contacts found.": "పరిచయాలు ఏవీ కనుగొనబడలేదు.", "No countries have been added yet in any contact’s addresses.": "ఏ సంప్రదింపు చిరునామాలలో ఇంకా దేశాలు జోడించబడలేదు.", @@ -652,6 +652,7 @@ "No life event types yet.": "జీవిత సంఘటనల రకాలు ఇంకా లేవు.", "No notes found.": "గమనికలు కనుగొనబడలేదు.", "No results found": "ఎటువంటి ఫలితాలు లభించలేదు", + "No role": "పాత్ర లేదు", "No roles yet.": "ఇంకా పాత్రలు లేవు.", "No tasks.": "పనులు లేవు.", "Notes": "గమనికలు", diff --git a/lang/tr.json b/lang/tr.json index 6f2997aead3..a8afe44ecb9 100644 --- a/lang/tr.json +++ b/lang/tr.json @@ -639,8 +639,8 @@ "New Password": "Yeni Şifre", "New to Monica?": "Monica'da yeni misiniz?", "Next": "Sonraki", - "nickname": "Takma ad", "Nickname": "Takma ad", + "nickname": "Takma ad", "No cities have been added yet in any contact’s addresses.": "Henüz herhangi bir kişinin adresine şehir eklenmemiş.", "No contacts found.": "Kişi bulunamadı.", "No countries have been added yet in any contact’s addresses.": "Henüz herhangi bir kişinin adresine ülke eklenmemiş.", @@ -652,6 +652,7 @@ "No life event types yet.": "Henüz yaşam olayı türü yok.", "No notes found.": "Not bulunamadı.", "No results found": "Sonuç bulunamadı", + "No role": "Rol yok", "No roles yet.": "Henüz rol yok.", "No tasks.": "Görev yok.", "Notes": "notlar", diff --git a/lang/ur.json b/lang/ur.json index 5a27900916f..3b4a3377774 100644 --- a/lang/ur.json +++ b/lang/ur.json @@ -639,8 +639,8 @@ "New Password": "نیا پاس ورڈ", "New to Monica?": "مونیکا کے لیے نئے ہیں؟", "Next": "اگلے", - "nickname": "عرفی نام", "Nickname": "عرفی نام", + "nickname": "عرفی نام", "No cities have been added yet in any contact’s addresses.": "کسی بھی رابطے کے پتے میں ابھی تک کوئی شہر شامل نہیں کیا گیا ہے۔", "No contacts found.": "کوئی رابطے نہیں ملے۔", "No countries have been added yet in any contact’s addresses.": "کسی بھی رابطے کے پتے میں ابھی تک کوئی ملک شامل نہیں کیا گیا ہے۔", @@ -652,6 +652,7 @@ "No life event types yet.": "ابھی تک زندگی کے واقعات کی کوئی قسم نہیں ہے۔", "No notes found.": "کوئی نوٹس نہیں ملا۔", "No results found": "کوئی نتیجہ نہیں", + "No role": "کوئی کردار نہیں۔", "No roles yet.": "ابھی تک کوئی کردار نہیں۔", "No tasks.": "کوئی کام نہیں۔", "Notes": "نوٹس", diff --git a/lang/vi.json b/lang/vi.json index ac066a5d58f..341ad11c2ce 100644 --- a/lang/vi.json +++ b/lang/vi.json @@ -639,8 +639,8 @@ "New Password": "mật khẩu mới", "New to Monica?": "Mới đến với Monica?", "Next": "Kế tiếp", - "nickname": "tên nick", "Nickname": "Tên nick", + "nickname": "tên nick", "No cities have been added yet in any contact’s addresses.": "Chưa có thành phố nào được thêm vào bất kỳ địa chỉ liên hệ nào.", "No contacts found.": "Không tìm thấy liên hệ nào.", "No countries have been added yet in any contact’s addresses.": "Chưa có quốc gia nào được thêm vào bất kỳ địa chỉ liên hệ nào.", @@ -652,6 +652,7 @@ "No life event types yet.": "Chưa có loại sự kiện cuộc sống nào.", "No notes found.": "Không tìm thấy ghi chú nào.", "No results found": "không có kết quả nào được tìm thấy", + "No role": "Không có vai trò", "No roles yet.": "Chưa có vai diễn nào.", "No tasks.": "Không có nhiệm vụ.", "Notes": "ghi chú", diff --git a/lang/zh.json b/lang/zh.json index b2255f4696a..b1f8462738d 100644 --- a/lang/zh.json +++ b/lang/zh.json @@ -639,8 +639,8 @@ "New Password": "新密码", "New to Monica?": "莫妮卡的新手?", "Next": "下一个", - "nickname": "昵称", "Nickname": "昵称", + "nickname": "昵称", "No cities have been added yet in any contact’s addresses.": "尚未在任何联系人的地址中添加任何城市。", "No contacts found.": "找不到联系人。", "No countries have been added yet in any contact’s addresses.": "尚未在任何联系人的地址中添加任何国家\/地区。", @@ -652,6 +652,7 @@ "No life event types yet.": "还没有生活事件类型。", "No notes found.": "未找到注释。", "No results found": "未找到结果", + "No role": "没有角色", "No roles yet.": "还没有角色。", "No tasks.": "没有任务。", "Notes": "笔记", diff --git a/resources/js/Pages/Vault/Group/Edit.vue b/resources/js/Pages/Vault/Group/Edit.vue index 44cf7fb2810..6c64dc54333 100644 --- a/resources/js/Pages/Vault/Group/Edit.vue +++ b/resources/js/Pages/Vault/Group/Edit.vue @@ -118,7 +118,6 @@ const update = () => { diff --git a/resources/js/Pages/Vault/Group/Show.vue b/resources/js/Pages/Vault/Group/Show.vue index 81719931227..0d778e0ca55 100644 --- a/resources/js/Pages/Vault/Group/Show.vue +++ b/resources/js/Pages/Vault/Group/Show.vue @@ -97,7 +97,7 @@ const destroy = () => { -
+
serialize() ); } + + /** + * @group dav + * + * @test + */ + public function it_exports_a_group_with_vcard() + { + $user = $this->createUser(); + $vault = $this->createVaultUser($user); + + $gender = Gender::factory()->create([ + 'account_id' => $vault->account_id, + 'type' => 'M', + ]); + + $vcard = 'BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 4.3.0//EN +SOURCE:**ANY** +UID:**ANY** +FN:Dr. John Doe III +N:Doe;John;;; +GENDER:M +REV:**ANY** +END:VCARD'; + + $contact = Contact::factory()->random()->create([ + 'vault_id' => $vault->id, + 'vcard' => $vcard, + 'first_name' => 'John', + 'last_name' => 'Doe', + 'gender_id' => $gender->id, + ]); + + $source = "BEGIN:VCARD +VERSION:4.0 +PRODID:-//Sabre//Sabre VObject 4.3.0//EN +SOURCE:**ANY** +MEMBER:{$contact->id} +UID:**ANY** +KIND:group +FN:Colleagues +N:Colleagues;;;; +REV:**ANY** +END:VCARD"; + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + 'name' => 'Colleagues', + 'vcard' => $source, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact->id => ['group_type_role_id' => null], + ]); + + $vCard = (new ExportVCard($this->app))->execute([ + 'account_id' => $user->account_id, + 'author_id' => $user->id, + 'vault_id' => $vault->id, + 'group_id' => $group->id, + ]); + + $this->assertInstanceOf(VCard::class, $vCard); + $this->assertVObjectEqualsVObject( + $source, + $vCard->serialize() + ); + } } diff --git a/tests/Unit/Domains/Contact/DAV/Services/ImportVCardTest.php b/tests/Unit/Domains/Contact/DAV/Services/ImportVCardTest.php index 7506189cf0b..e89095af5de 100644 --- a/tests/Unit/Domains/Contact/DAV/Services/ImportVCardTest.php +++ b/tests/Unit/Domains/Contact/DAV/Services/ImportVCardTest.php @@ -3,6 +3,8 @@ namespace Tests\Unit\Domains\Contact\DAV\Services; use App\Domains\Contact\Dav\Services\ImportVCard; +use App\Models\Contact; +use App\Models\Group; use App\Models\User; use App\Models\Vault; use Illuminate\Foundation\Testing\DatabaseTransactions; @@ -19,7 +21,7 @@ class ImportVCardTest extends TestCase /** @test */ public function it_can_not_import_because_no_firstname_or_nickname_in_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([]); @@ -29,7 +31,7 @@ public function it_can_not_import_because_no_firstname_or_nickname_in_vcard() /** @test */ public function it_can_not_import_because_no_firstname_in_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'N' => ['John', '', '', '', ''], @@ -41,7 +43,7 @@ public function it_can_not_import_because_no_firstname_in_vcard() /** @test */ public function it_can_not_import_because_empty_firstname_in_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'N' => ';;;;', @@ -53,7 +55,7 @@ public function it_can_not_import_because_empty_firstname_in_vcard() /** @test */ public function it_can_not_import_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = Reader::read(' BEGIN:VCARD @@ -74,7 +76,7 @@ public function it_can_not_import_vcard() /** @test */ public function it_can_not_import_because_empty_nickname_in_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'NICKNAME' => '', @@ -86,7 +88,7 @@ public function it_can_not_import_because_empty_nickname_in_vcard() /** @test */ public function it_can_not_import_because_empty_fullname_in_vcard() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'FN' => '', @@ -98,7 +100,7 @@ public function it_can_not_import_because_empty_fullname_in_vcard() /** @test */ public function it_can_import_firstname() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'N' => ['', 'John', '', '', ''], @@ -110,7 +112,7 @@ public function it_can_import_firstname() /** @test */ public function it_can_import_nickname() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'NICKNAME' => 'John', @@ -122,7 +124,7 @@ public function it_can_import_nickname() /** @test */ public function it_can_import_fullname() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $vcard = new VCard([ 'FN' => 'John Doe', @@ -134,7 +136,7 @@ public function it_can_import_fullname() /** @test */ public function it_formats_value() { - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $result = $this->invokePrivateMethod($importVCard, 'formatValue', ['']); $this->assertNull($result); @@ -151,7 +153,7 @@ public function it_creates_a_contact() { $author = User::factory()->create(); $vault = $this->createVaultUser($author, Vault::PERMISSION_EDIT); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importVCard->vault = $vault; @@ -165,12 +167,85 @@ public function it_creates_a_contact() $this->assertTrue($contact->exists); } + /** @test */ + public function it_creates_a_contact_full() + { + $author = User::factory()->create(); + $vault = $this->createVaultUser($author, Vault::PERMISSION_EDIT); + + $vcard = 'BEGIN:VCARD +VERSION:3.0 +UID:31fdc242-c974-436e-98de-6b21624d6e34 +N:John;Doe;;; +FN:John Doe +REV:20210900T000102Z +END:VCARD'; + + (new ImportVCard())->execute([ + 'account_id' => $author->account_id, + 'author_id' => $author->id, + 'vault_id' => $vault->id, + 'entry' => $vcard, + 'behaviour' => 'behaviour_add', + 'external' => true, + ]); + + $this->assertDatabaseHas('contacts', [ + 'distant_uuid' => '31fdc242-c974-436e-98de-6b21624d6e34', + 'vault_id' => $vault->id, + 'first_name' => 'Doe', + 'last_name' => 'John', + ]); + } + + /** @test */ + public function it_creates_a_group_full() + { + $author = User::factory()->create(); + $vault = $this->createVaultUser($author, Vault::PERMISSION_EDIT); + + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + + $vcard = "BEGIN:VCARD +VERSION:3.0 +UID:61fdc242-c974-436e-98de-6b21624d6e35 +KIND:group +N:Colleagues;;;; +FN:Colleagues +MEMBER:{$contact->id} +REV:20210900T000102Z +END:VCARD"; + + (new ImportVCard())->execute([ + 'account_id' => $author->account_id, + 'author_id' => $author->id, + 'vault_id' => $vault->id, + 'entry' => $vcard, + 'behaviour' => 'behaviour_add', + 'external' => true, + ]); + + $this->assertDatabaseHas('groups', [ + 'distant_uuid' => '61fdc242-c974-436e-98de-6b21624d6e35', + 'vault_id' => $vault->id, + 'name' => 'Colleagues', + ]); + + $group = Group::firstWhere('distant_uuid', '61fdc242-c974-436e-98de-6b21624d6e35'); + $this->assertDatabaseHas('contact_group', [ + 'group_id' => $group->id, + 'contact_id' => $contact->id, + ]); + } + /** @test */ public function it_imports_uuid_contact() { $author = User::factory()->create(); $vault = $this->createVaultUser($author, Vault::PERMISSION_EDIT); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importVCard->vault = $vault; diff --git a/tests/Unit/Domains/Contact/ManageContact/Dav/ImportContactTest.php b/tests/Unit/Domains/Contact/ManageContact/Dav/ImportContactTest.php index c94e3d68b91..9252fd7d871 100644 --- a/tests/Unit/Domains/Contact/ManageContact/Dav/ImportContactTest.php +++ b/tests/Unit/Domains/Contact/ManageContact/Dav/ImportContactTest.php @@ -48,7 +48,7 @@ public function it_imports_names_NICKNAME() public function it_imports_names_FN() { $author = User::factory()->create(); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); @@ -68,7 +68,7 @@ public function it_imports_names_FN_last() $author = User::factory()->create([ 'name_order' => '%last_name% %first_name%', ]); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); @@ -86,7 +86,7 @@ public function it_imports_names_FN_last() public function it_imports_names_FN_extra_space() { $author = User::factory()->create(); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); @@ -104,7 +104,7 @@ public function it_imports_names_FN_extra_space() public function it_imports_name_FN() { $author = User::factory()->create(); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); @@ -125,7 +125,7 @@ public function it_imports_name_FN_last() $author = User::factory()->create([ 'name_order' => '%last_name% %first_name%', ]); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); @@ -144,7 +144,7 @@ public function it_imports_name_FN_last() public function it_imports_names_FN_multiple() { $author = User::factory()->create(); - $importVCard = new ImportVCard($this->app); + $importVCard = new ImportVCard(); $importVCard->author = $author; $importContact = new ImportContact(); $importContact->setContext($importVCard); diff --git a/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportMembersTest.php b/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportMembersTest.php new file mode 100644 index 00000000000..21670f28a70 --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportMembersTest.php @@ -0,0 +1,106 @@ +createVaultUser(User::factory()->create(), Vault::PERMISSION_MANAGE); + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact->id => ['group_type_role_id' => null], + ]); + + $exportMembers = new ExportMembers(); + + $vcard = new VCard([ + 'KIND' => 'group', + ]); + + $exportMembers->export($group, $vcard); + + $members = collect($vcard->select('MEMBER'))->map(fn ($member): string => (string) $member); + + $this->assertCount(1, $members); + $this->assertContains($contact->id, $members); + } + + /** @test */ + public function it_exports_removing_members() + { + $vault = $this->createVaultUser(User::factory()->create(), Vault::PERMISSION_MANAGE); + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact->id => ['group_type_role_id' => null], + ]); + + $exportMembers = new ExportMembers(); + + $vcard = new VCard([ + 'KIND' => 'group', + 'MEMBER' => 'false', + ]); + + $exportMembers->export($group, $vcard); + + $members = collect($vcard->select('MEMBER'))->map(fn ($member): string => (string) $member); + + $this->assertCount(1, $members); + $this->assertContains($contact->id, $members); + $this->assertNotContains('false', $members); + } + + /** @test */ + public function it_exports_keeping_members() + { + $vault = $this->createVaultUser(User::factory()->create(), Vault::PERMISSION_MANAGE); + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact->id => ['group_type_role_id' => null], + ]); + + $exportMembers = new ExportMembers(); + + $vcard = new VCard([ + 'KIND' => 'group', + 'MEMBER' => $contact->id, + ]); + + $exportMembers->export($group, $vcard); + + $members = collect($vcard->select('MEMBER'))->map(fn ($member): string => (string) $member); + + $this->assertCount(1, $members); + $this->assertContains($contact->id, $members); + } +} diff --git a/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportNamesTest.php b/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportNamesTest.php new file mode 100644 index 00000000000..208b3dcb533 --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageGroups/Dav/ExportNamesTest.php @@ -0,0 +1,66 @@ +createUser(); + $vault = $this->createVaultUser($user); + $contact = Contact::factory()->random()->create([ + 'vault_id' => $vault->id, + ]); + + $vCard = new VCard(); + (new ExportNames)->export($contact, $vCard); + + $this->assertCount( + self::defaultPropsCount + 2, + $vCard->children() + ); + $this->assertStringContainsString("FN:{$contact->name}", $vCard->serialize()); + $this->assertStringContainsString("N:{$contact->last_name};{$contact->first_name};;;", $vCard->serialize()); + } + + /** + * @group dav + * + * @test + */ + public function it_adds_nickname_in_vcard() + { + $user = $this->createUser(); + $vault = $this->createVaultUser($user); + $contact = Contact::factory()->random()->nickname()->create([ + 'vault_id' => $vault->id, + ]); + + $vCard = new VCard(); + (new ExportNames)->export($contact, $vCard); + + $this->assertCount( + self::defaultPropsCount + 3, + $vCard->children() + ); + $this->assertStringContainsString("FN:{$contact->name}", $vCard->serialize()); + $this->assertStringContainsString("N:{$contact->last_name};{$contact->first_name};;;", $vCard->serialize()); + $this->assertStringContainsString("NICKNAME:{$contact->nickname}", $vCard->serialize()); + } +} diff --git a/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportGroupTest.php b/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportGroupTest.php new file mode 100644 index 00000000000..73f3566f6bb --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportGroupTest.php @@ -0,0 +1,112 @@ + ['NameGroup', '', '', '', ''], + ]); + $group = $importGroup->importNames([], $vcard); + + $this->assertEquals('NameGroup', $group['name']); + } + + /** @test */ + public function it_imports_names_FN() + { + $author = User::factory()->create(); + $importVCard = new ImportVCard(); + $importVCard->author = $author; + $importGroup = new ImportGroup(); + $importGroup->setContext($importVCard); + + $vcard = new VCard([ + 'FN' => 'Name Group', + ]); + $group = $importGroup->importNames([], $vcard); + + $this->assertEquals('Name Group', $group['name']); + } + + /** @test */ + public function it_imports_name_FN() + { + $author = User::factory()->create(); + $importVCard = new ImportVCard(); + $importVCard->author = $author; + $importGroup = new ImportGroup(); + $importGroup->setContext($importVCard); + + $vcard = new VCard([ + 'FN' => 'Other Name', + 'N' => 'Name Group', + ]); + $group = $importGroup->importNames([], $vcard); + + $this->assertEquals('Name Group', $group['name']); + } + + /** @test */ + public function it_imports_uuid_default() + { + $importGroup = new ImportGroup(); + $importGroup->setContext(new ImportVCard($this->app)); + + $vcard = new VCard([ + 'UID' => '31fdc242-c974-436e-98de-6b21624d6e34', + ]); + + $group = []; + + $group = $this->invokePrivateMethod($importGroup, 'importUid', [$group, $vcard]); + + $this->assertEquals('31fdc242-c974-436e-98de-6b21624d6e34', $group['id']); + } + + /** @test */ + public function it_updates_name() + { + $vault = $this->createVaultUser($user = User::factory()->create(), Vault::PERMISSION_MANAGE); + $importGroup = new ImportGroup(); + $importGroup->setContext(tap(new ImportVCard($this->app), function ($importVCard) use ($user, $vault) { + $importVCard->author = $user; + $importVCard->vault = $vault; + })); + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + 'distant_uuid' => '31fdc242-c974-436e-98de-6b21624d6e34', + ]); + + $vcard = new VCard([ + 'UID' => '31fdc242-c974-436e-98de-6b21624d6e34', + 'KIND' => 'group', + 'FN' => 'New Name', + ]); + + $importGroup->import($vcard, $group); + + $group->refresh(); + + $this->assertEquals('New Name', $group->name); + } +} diff --git a/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportMembersTest.php b/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportMembersTest.php new file mode 100644 index 00000000000..1faf354af59 --- /dev/null +++ b/tests/Unit/Domains/Contact/ManageGroups/Dav/ImportMembersTest.php @@ -0,0 +1,188 @@ +setContext(new ImportVCard($this->app)); + + $vcard = new VCard([ + 'MEMBER' => '31fdc242-c974-436e-98de-6b21624d6e34', + ]); + + $members = $this->invokePrivateMethod($importGroup, 'importMembers', [$vcard]); + + $this->assertContains('31fdc242-c974-436e-98de-6b21624d6e34', $members); + } + + /** @test */ + public function it_imports_multiple_members() + { + $importGroup = new ImportMembers(); + $importGroup->setContext(new ImportVCard($this->app)); + + $vcard = new VCard(); + $vcard->add('MEMBER', '31fdc242-c974-436e-98de-6b21624d6e34'); + $vcard->add('MEMBER', '61fdc242-c974-436e-98de-6b21624d6e34'); + + $data = []; + + $members = $this->invokePrivateMethod($importGroup, 'importMembers', [$vcard]); + + $this->assertContains('31fdc242-c974-436e-98de-6b21624d6e34', $members); + $this->assertContains('61fdc242-c974-436e-98de-6b21624d6e34', $members); + } + + /** @test */ + public function it_updates_members() + { + $vault = $this->createVaultUser($user = User::factory()->create(), Vault::PERMISSION_MANAGE); + $importGroup = new ImportMembers(); + $importGroup->setContext(tap(new ImportVCard($this->app), function ($importVCard) use ($user, $vault) { + $importVCard->author = $user; + $importVCard->vault = $vault; + })); + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + + $members = collect([ + $contact->id, + ]); + + $this->invokePrivateMethod($importGroup, 'updateGroupMembers', [$group, $members]); + + $group = $group->refresh(); + $c = $group->contacts->first(); + + $this->assertEquals($contact->id, $c->id); + } + + /** @test */ + public function it_keeps_existing_members() + { + $vault = $this->createVaultUser($user = User::factory()->create(), Vault::PERMISSION_MANAGE); + $importGroup = new ImportMembers(); + $importGroup->setContext(tap(new ImportVCard($this->app), function ($importVCard) use ($user, $vault) { + $importVCard->author = $user; + $importVCard->vault = $vault; + })); + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact->id => ['group_type_role_id' => null], + ]); + + $members = collect([ + $contact->id, + ]); + + $this->invokePrivateMethod($importGroup, 'updateGroupMembers', [$group, $members]); + + $group = $group->refresh(); + $c = $group->contacts->first(); + + $this->assertEquals($contact->id, $c->id); + } + + /** @test */ + public function it_keeps_existing_members_and_add_new() + { + $vault = $this->createVaultUser($user = User::factory()->create(), Vault::PERMISSION_MANAGE); + $importGroup = new ImportMembers(); + $importGroup->setContext(tap(new ImportVCard($this->app), function ($importVCard) use ($user, $vault) { + $importVCard->author = $user; + $importVCard->vault = $vault; + })); + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact1 = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact1->id => ['group_type_role_id' => null], + ]); + $contact2 = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + + $members = [ + $contact1->id, + $contact2->id, + ]; + + $this->invokePrivateMethod($importGroup, 'updateGroupMembers', [$group, collect($members)]); + + $group = $group->refresh(); + $contacts = collect($group->contacts->all())->map(fn ($c) => $c->id)->toArray(); + + $this->assertEquals($members, $contacts); + } + + /** @test */ + public function it_removes_old_members() + { + $vault = $this->createVaultUser($user = User::factory()->create(), Vault::PERMISSION_MANAGE); + $importGroup = new ImportMembers(); + $importGroup->setContext(tap(new ImportVCard($this->app), function ($importVCard) use ($user, $vault) { + $importVCard->author = $user; + $importVCard->vault = $vault; + })); + + $group = Group::factory()->create([ + 'vault_id' => $vault->id, + ]); + $contact1 = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact1->id => ['group_type_role_id' => null], + ]); + $contact2 = Contact::factory()->create([ + 'vault_id' => $vault->id, + ]); + $group->contacts()->syncWithoutDetaching([ + $contact2->id => ['group_type_role_id' => null], + ]); + + $members = [ + $contact2->id, + ]; + + $this->invokePrivateMethod($importGroup, 'updateGroupMembers', [$group, collect($members)]); + + $group = $group->refresh(); + $contacts = collect($group->contacts->all())->map(fn ($c) => $c->id)->toArray(); + + $this->assertEquals($members, $contacts); + } +}