Files
monica/app/Jobs/AddContactFromVCard.php
T

219 lines
7.3 KiB
PHP

<?php
namespace App\Jobs;
use App\User;
use App\Contact;
use App\Reminder;
use App\ImportJob;
use App\ImportJobReport;
use App\Country;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Sabre\VObject\Component\VCard;
use Sabre\VObject\Property\ICalendar\DateTime;
use Sabre\VObject\Reader;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Support\Facades\Storage;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
class AddContactFromVCard implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
const VCARD_SKIPPED = 1;
const VCARD_IMPORTED = 0;
const ERROR_CONTACT_EXIST = 'import_vcard_contact_exist';
const ERROR_CONTACT_DOESNT_HAVE_FIRSTNAME = 'import_vcard_contact_no_firstname';
protected $importJob;
protected $importedContacts = 0;
protected $skippedContacts = 0;
/**
* Create a new job instance.
*
* @return void
*/
public function __construct(ImportJob $importJob)
{
$this->importJob = $importJob;
}
/**
* Execute the job.
*
* @return void
*/
public function handle()
{
try {
$numberOfContactsInTheFile = preg_match_all('/(BEGIN:VCARD.*?END:VCARD)/s', Storage::disk('public')->get($this->importJob->filename), $matches);
$this->importJob->started_at = \Carbon\Carbon::now();
collect($matches[0])->map(function ($vcard) {
return Reader::read($vcard);
})->each(function (VCard $vcard) {
if ($this->contactExists($vcard, $this->importJob->account_id)) {
$this->skippedContacts++;
$this->fileImportJobReport($vcard, self::VCARD_SKIPPED, self::ERROR_CONTACT_EXIST);
return;
}
// Skip contact if there isn't a first name or a nickname
if (! $this->contactHasName($vcard)) {
$this->skippedContacts++;
$this->fileImportJobReport($vcard, self::VCARD_SKIPPED, self::ERROR_CONTACT_DOESNT_HAVE_FIRSTNAME);
return;
}
$contact = new Contact();
$contact->account_id = $this->importJob->account_id;
if($vcard->N && ! empty($vcard->N->getParts()[1])) {
$contact->first_name = $this->formatValue($vcard->N->getParts()[1]);
$contact->middle_name = $this->formatValue($vcard->N->getParts()[2]);
$contact->last_name = $this->formatValue($vcard->N->getParts()[0]);
} else {
$contact->first_name = $this->formatValue($vcard->NICKNAME);
}
$contact->gender = 'none';
$contact->is_birthdate_approximate = 'unknown';
if ($vcard->BDAY && !empty((string) $vcard->BDAY)) {
$contact->is_birthdate_approximate = 'exact';
$contact->birthdate = new \DateTime((string) $vcard->BDAY);
}
$contact->email = $this->formatValue($vcard->EMAIL);
$contact->phone_number = $this->formatValue($vcard->TEL);
if($vcard->ADR) {
$contact->street = $this->formatValue($vcard->ADR->getParts()[2]);
$contact->city = $this->formatValue($vcard->ADR->getParts()[3]);
$contact->province = $this->formatValue($vcard->ADR->getParts()[4]);
$contact->postal_code = $this->formatValue($vcard->ADR->getParts()[5]);
$country = Country::where('country', $vcard->ADR->getParts()[6])
->orWhere('iso', strtolower($vcard->ADR->getParts()[6]))
->first();
if ($country) {
$contact->country_id = $country->id;
}
}
$contact->job = $this->formatValue($vcard->ORG);
$contact->setAvatarColor();
$contact->save();
// if birthdate is known, we need to create reminders
$reminder = Reminder::addBirthdayReminder(
$contact,
trans(
'people.people_add_birthday_reminder',
['name' => $contact->name]
),
$contact->birthdate
);
$contact->update([
'birthday_reminder_id' => $reminder->id,
]);
$this->importedContacts++;
$this->fileImportJobReport($vcard, self::VCARD_IMPORTED);
$contact->logEvent('contact', $contact->id, 'create');
});
$this->importJob->contacts_found = $numberOfContactsInTheFile;
$this->importJob->contacts_skipped = $this->skippedContacts;
$this->importJob->contacts_imported = $this->importedContacts;
$this->importJob->ended_at = \Carbon\Carbon::now();
$this->importJob->save();
} catch (Exception $e) {
$this->importJob->contacts_found = $numberOfContactsInTheFile;
$this->importJob->failed = 1;
$this->importJob->failed_reason = $e->getMessage();
$this->importJob->save();
Storage::disk('public')->delete($this->importJob->filename);
}
// Delete the vCard file no matter what
Storage::disk('public')->delete($this->importJob->filename);
}
/**
* Checks whether a contact has a first name or a nickname.
* Nickname is used as a fallback if no first name is provided.
*
* @param VCard $vcard
* @return bool
*/
function contactHasName(VCard $vcard): bool
{
return ! empty($vcard->N->getParts()[1]) || ! empty((string) $vcard->NICKNAME);
}
/**
* Formats and returns a string for the contact
*
* @param null|string $value
* @return null|string
*/
private function formatValue($value)
{
return !empty((string) $value) ? (string) $value : null;
}
/**
* Checks whether a contact already exists for a given account
*
* @param VCard $vcard
* @param User $user
* @return bool
*/
private function contactExists(VCard $vcard, $account_id)
{
$email = (string) $vcard->EMAIL;
$contact = Contact::where([
['account_id', $account_id],
['email', $email]
])->first();
return $email && $contact;
}
private function fileImportJobReport(VCard $vcard, $status, $reason = null)
{
$name = $this->formatValue($vcard->N->getParts()[1]);
$name .= ' ' . $this->formatValue($vcard->N->getParts()[2]);
$name .= ' ' . $this->formatValue($vcard->N->getParts()[0]);
$name .= ' ' . $this->formatValue($vcard->EMAIL);
$importJobReport = new ImportJobReport;
$importJobReport->account_id = $this->importJob->account_id;
$importJobReport->user_id = $this->importJob->user_id;
$importJobReport->import_job_id = $this->importJob->id;
$importJobReport->contact_information = trim($name);
$importJobReport->skipped = $status;
$importJobReport->skip_reason = $reason;
$importJobReport->save();
}
}