app/Services/VCard/ExportVCard.php (200 lines of code) (raw):

<?php namespace App\Services\VCard; use Illuminate\Support\Str; use App\Services\BaseService; use App\Models\Contact\Gender; use App\Models\Contact\Contact; use App\Interfaces\LabelInterface; use Sabre\VObject\Component\VCard; use App\Models\Contact\ContactFieldType; use App\Models\Contact\ContactFieldLabel; class ExportVCard extends BaseService { /** * Get the validation rules that apply to the service. * * @return array */ public function rules() { return [ 'account_id' => 'required|integer|exists:accounts,id', 'contact_id' => 'required|integer|exists:contacts,id', ]; } /** * Export one VCard. * * @param array $data * @return VCard */ public function execute(array $data): VCard { $this->validate($data); $contact = Contact::where('account_id', $data['account_id']) ->findOrFail($data['contact_id']); return $this->export($contact); } private function escape($value): string { return ! empty((string) $value) ? trim((string) $value) : (string) null; } /** * @param Contact $contact * @return VCard */ private function export(Contact $contact): VCard { // The standard for most of these fields can be found on https://tools.ietf.org/html/rfc6350 if (! $contact->uuid) { $contact->forceFill([ 'uuid' => Str::uuid(), ])->save(); } // Basic information $vcard = new VCard([ 'UID' => $contact->uuid, 'SOURCE' => $contact->getLink(), 'VERSION' => '4.0', ]); $this->exportNames($contact, $vcard); $this->exportGender($contact, $vcard); $this->exportPhoto($contact, $vcard); $this->exportWorkInformation($contact, $vcard); $this->exportBirthday($contact, $vcard); $this->exportAddress($contact, $vcard); $this->exportContactFields($contact, $vcard); $this->exportTimestamp($contact, $vcard); $this->exportTags($contact, $vcard); return $vcard; } /** * @param Contact $contact * @param VCard $vcard */ private function exportNames(Contact $contact, VCard $vcard) { $vcard->add('FN', $this->escape($contact->name)); $vcard->add('N', [ $this->escape($contact->last_name), $this->escape($contact->first_name), $this->escape($contact->middle_name), ]); if (! empty($contact->nickname)) { $vcard->add('NICKNAME', $this->escape($contact->nickname)); } } /** * @param Contact $contact * @param VCard $vcard */ private function exportGender(Contact $contact, VCard $vcard) { if (is_null($contact->gender)) { return; } $gender = $contact->gender->type; if (empty($gender)) { switch ($contact->gender->name) { case trans('app.gender_male'): $gender = Gender::MALE; break; case trans('app.gender_female'): $gender = Gender::FEMALE; break; default: $gender = Gender::OTHER; break; } } $vcard->add('GENDER', $gender); } /** * @param Contact $contact * @param VCard $vcard */ private function exportPhoto(Contact $contact, VCard $vcard) { if ($contact->avatar_source == 'photo') { $photo = $contact->avatarPhoto; $vcard->add('PHOTO', $photo->dataUrl()); } else { $picture = $contact->getAvatarURL(); if (! empty($picture)) { $vcard->add('PHOTO', $picture); } } } /** * @param Contact $contact * @param VCard $vcard */ private function exportWorkInformation(Contact $contact, VCard $vcard) { if (! empty($contact->company)) { $vcard->add('ORG', $this->escape($contact->company)); } if (! empty($contact->job)) { $vcard->add('TITLE', $this->escape($contact->job)); } } /** * @param Contact $contact * @param VCard $vcard */ private function exportBirthday(Contact $contact, VCard $vcard) { if (! is_null($contact->birthdate)) { if ($contact->birthdate->is_year_unknown) { $date = $contact->birthdate->date->format('--m-d'); } else { $date = $contact->birthdate->date->format('Ymd'); } $vcard->add('BDAY', $date); } } /** * @param Contact $contact * @param VCard $vcard * @see https://tools.ietf.org/html/rfc6350#section-6.3.1 */ private function exportAddress(Contact $contact, VCard $vcard) { foreach ($contact->addresses as $address) { $type = $this->getContactFieldLabel($address); $arguments = []; if ($type != '') { $arguments['TYPE'] = $type; } $vcard->add('ADR', [ '', '', $address->place->street, $address->place->city, $address->place->province, $address->place->postal_code, $address->place->country, ], $arguments ); } } /** * @param Contact $contact * @param VCard $vcard */ private function exportContactFields(Contact $contact, VCard $vcard) { foreach ($contact->contactFields as $contactField) { $type = $this->getContactFieldLabel($contactField); switch ($contactField->contactFieldType->type) { case ContactFieldType::PHONE: $vcard->add('TEL', $this->escape($contactField->data), $type); break; case ContactFieldType::EMAIL: $vcard->add('EMAIL', $this->escape($contactField->data), $type); break; default: break; } switch ($contactField->contactFieldType->name) { // See https://tools.ietf.org/id/draft-george-vcarddav-vcard-extension-02.html case 'Facebook': $vcard->add('socialProfile', $this->escape('https://www.facebook.com/'.$contactField->data), ['type' => 'facebook']); break; case 'Twitter': $vcard->add('socialProfile', $this->escape('https://twitter.com/'.$contactField->data), ['type' => 'twitter']); break; case 'Whatsapp': $vcard->add('socialProfile', $this->escape('https://wa.me/'.$contactField->data), ['type' => 'whatsapp']); break; case 'Telegram': $vcard->add('socialProfile', $this->escape('http://t.me/'.$contactField->data), ['type' => 'telegram']); break; case 'LinkedIn': $vcard->add('socialProfile', $this->escape('http://www.linkedin.com/in/'.$contactField->data), ['type' => 'linkedin']); break; default: break; } } } /** * @param LabelInterface $labelProvider * @return array|null */ private function getContactFieldLabel(LabelInterface $labelProvider): ?array { $type = null; $labels = $labelProvider->labels()->get(); if ($labels->count() > 0) { $type = []; $type['type'] = $labels->map(function ($label) { /** @var ContactFieldLabel */ $cflabel = $label; return mb_strtoupper($cflabel->label_i18n) ?: $cflabel->label; })->join(','); } return $type; } /** * @param Contact $contact * @param VCard $vcard */ private function exportTimestamp(Contact $contact, VCard $vcard) { $vcard->REV = $contact->updated_at->format('Ymd\\THis\\Z'); } /** * @param Contact $contact * @param VCard $vcard */ private function exportTags(Contact $contact, VCard $vcard) { if ($contact->tags->count() > 0) { $vcard->CATEGORIES = $contact->tags->map(function ($tag) { return $tag->name; })->toArray(); } } }