belongsTo(Account::class); } /** * Get the user record associated with the gift. * * @return BelongsTo */ public function user() { return $this->belongsTo(User::class); } /** * Get the import jobs reports records associated with the account. * * @return HasMany */ public function importJobReports() { return $this->hasMany(ImportJobReport::class); } /** * Process an import job. * * @return [type] [description] */ public function process($behaviour = self::BEHAVIOUR_ADD) { $this->initJob(); $this->getPhysicalFile(); $this->getEntries(); $this->getSpecialGender(); $this->processEntries($behaviour); $this->deletePhysicalFile(); $this->endJob(); } /** * Perform preliminary steps to start the import job. * * @return void */ public function initJob(): void { $this->started_at = now(); $this->contacts_imported = 0; $this->contacts_skipped = 0; $this->save(); } /** * Perform the steps to finalize the import job. * * @return void */ public function endJob(): void { $this->ended_at = now(); $this->save(); } /** * Get or create the gender called "Vcard" that is associated with all * imported contacts. * * @return \App\Gender */ public function getSpecialGender() { $this->gender = \App\Gender::where('name', 'vCard')->first(); if (! $this->gender) { $this->gender = new \App\Gender; $this->gender->account_id = $this->account_id; $this->gender->name = 'vCard'; $this->gender->save(); } } /** * Mark the import job as failed. * * @param string $reason * @return Exception */ public function fail(string $reason) { $this->failed = true; $this->failed_reason = $reason; $this->endJob(); } /** * Get the physical file (the vCard file). * * @return $this */ public function getPhysicalFile() { try { $this->physicalFile = Storage::disk('public')->get($this->filename); } catch (FileNotFoundException $exception) { $this->fail(trans('settings.import_vcard_file_not_found')); } return $this; } /** * Delete the physical file from the disk. * * @return $this */ public function deletePhysicalFile() { if (! Storage::disk('public')->delete($this->filename)) { $this->fail(trans('settings.import_vcard_file_not_found')); } } /** * Get the number of matches in the vCard file. * * @return void */ public function getEntries() { $this->contacts_found = preg_match_all('/(BEGIN:VCARD.*?END:VCARD)/s', $this->physicalFile, $this->entries); $this->contacts_found = count($this->entries[0]); if ($this->contacts_found == 0) { $this->fail(trans('settings.import_vcard_file_no_entries')); } } /** * Process all entries contained in the vCard file. * * @return */ public function processEntries($behaviour = self::BEHAVIOUR_ADD) { collect($this->entries[0])->map(function ($vcard) { return Reader::read($vcard); })->each(function (VCard $vCard) use ($behaviour) { $this->currentEntry = $vCard; $this->processSingleEntry($behaviour); }); } /** * Process a single vCard entry. * * @param VCard $vCard * @return [type] [description] */ public function processSingleEntry($behaviour = self::BEHAVIOUR_ADD) { if (! $this->checkImportFeasibility()) { $this->skipEntry(self::ERROR_CONTACT_DOESNT_HAVE_FIRSTNAME); return; } $contact = $this->existingContact(); if ($contact && $behaviour === self::BEHAVIOUR_ADD) { $this->skipEntry(self::ERROR_CONTACT_EXIST); return; } $this->createContactFromCurrentEntry($contact); } /** * Skip the current entry. * * @param string $reason * @return void */ public function skipEntry($reason = null): void { $this->fileImportJobReport(self::VCARD_SKIPPED, $reason); $this->contacts_skipped++; } /** * Check whether a contact has a first name or a nickname. If not, contact * can not be imported. * * @param VCard $vcard * @return bool */ public function checkImportFeasibility(): bool { if (is_null($this->currentEntry->N)) { return false; } return ! empty($this->currentEntry->N->getParts()[1]) || ! empty((string) $this->currentEntry->NICKNAME); } /** * Check whether the email is valid. * * @param string $email * @return bool */ public function isValidEmail(string $email): bool { return filter_var($email, FILTER_VALIDATE_EMAIL); } /** * Check whether the contact already exists in the database. * * @return Contact|null */ public function existingContact() { if (is_null($this->currentEntry->EMAIL)) { return; } $email = (string) $this->currentEntry->EMAIL; if (! $this->isValidEmail($email)) { return; } $contactFieldType = \App\ContactFieldType::where([ ['account_id', $this->account_id], ['type', 'email'], ])->first(); if ($contactFieldType) { $contactField = \App\ContactField::where([ ['account_id', $this->account_id], ['contact_field_type_id', $contactFieldType->id], ])->whereIn('data', iterator_to_array($this->currentEntry->EMAIL))->first(); if ($contactField) { return $contactField->contact; } } } /** * File an import job report for the current entry. * * @param bool $status * @param string $reason * @return void */ public function fileImportJobReport($status, $reason = null): void { $name = $this->name(); $importJobReport = new \App\ImportJobReport; $importJobReport->account_id = $this->account_id; $importJobReport->user_id = $this->user_id; $importJobReport->import_job_id = $this->id; $importJobReport->contact_information = trim($name); $importJobReport->skipped = $status; $importJobReport->skip_reason = $reason; $importJobReport->save(); } /** * Return the name and email address of the current entry. * John Doe Johnny john@doe.com. * * @return string */ public function name(): string { if (is_null($this->currentEntry->N)) { return trans('settings.import_vcard_unknown_entry'); } $name = $this->formatValue($this->currentEntry->N->getParts()[1]); $name .= ' '.$this->formatValue($this->currentEntry->N->getParts()[2]); $name .= ' '.$this->formatValue($this->currentEntry->N->getParts()[0]); $name .= ' '.$this->formatValue($this->currentEntry->EMAIL); return $name; } /** * 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; } /** * Create the Contact object matching the current entry. * * @return Contact */ public function createContactFromCurrentEntry($contact = null) { if (! $contact) { $contact = new \App\Contact; $contact->account_id = $this->account_id; $contact->gender_id = $this->gender->id; $contact->save(); } $this->importNames($contact); $this->importWorkInformation($contact); $this->importBirthday($contact); $this->importAddress($contact); $this->importEmail($contact); $this->importTel($contact); $this->contacts_imported++; $this->fileImportJobReport(self::VCARD_IMPORTED); $contact->setAvatarColor(); $contact->save(); return $contact; } /** * Import names of the contact. * * @param Contact $contact * @return void */ public function importNames(\App\Contact $contact): void { if ($this->currentEntry->N && ! empty($this->currentEntry->N->getParts()[1])) { $contact->first_name = $this->formatValue($this->currentEntry->N->getParts()[1]); $contact->middle_name = $this->formatValue($this->currentEntry->N->getParts()[2]); $contact->last_name = $this->formatValue($this->currentEntry->N->getParts()[0]); } else { $contact->first_name = $this->formatValue($this->currentEntry->NICKNAME); } } /** * @param Contact $contact * @return void */ public function importWorkInformation(\App\Contact $contact): void { if ($this->currentEntry->ORG) { $contact->company = $this->formatValue($this->currentEntry->ORG); } if ($this->currentEntry->ROLE) { $contact->job = $this->formatValue($this->currentEntry->ROLE); } if ($this->currentEntry->TITLE) { $contact->job = $this->formatValue($this->currentEntry->TITLE); } } /** * @param Contact $contact * @return void */ public function importBirthday(\App\Contact $contact): void { if ($this->currentEntry->BDAY && ! empty((string) $this->currentEntry->BDAY)) { $birthdate = new \DateTime((string) $this->currentEntry->BDAY); $specialDate = $contact->setSpecialDate('birthdate', $birthdate->format('Y'), $birthdate->format('m'), $birthdate->format('d')); $specialDate->setReminder('year', 1, trans('people.people_add_birthday_reminder', ['name' => $contact->first_name])); } } /** * @param Contact $contact * @return void */ public function importAddress(\App\Contact $contact): void { if (! $this->currentEntry->ADR) { return; } foreach ($this->currentEntry->ADR as $adr) { Address::firstOrCreate([ 'account_id' => $contact->account_id, 'contact_id' => $contact->id, 'street' => $this->formatValue($adr->getParts()[2]), 'city' => $this->formatValue($adr->getParts()[3]), 'province' => $this->formatValue($adr->getParts()[4]), 'postal_code' => $this->formatValue($adr->getParts()[5]), 'country' => CountriesHelper::find($adr->getParts()[6]), ]); } } /** * @param Contact $contact * @return void */ public function importEmail(\App\Contact $contact): void { if (is_null($this->currentEntry->EMAIL)) { return; } foreach ($this->currentEntry->EMAIL as $email) { if ($this->isValidEmail($email)) { ContactField::firstOrCreate([ 'account_id' => $contact->account_id, 'contact_id' => $contact->id, 'data' => $this->formatValue($email), 'contact_field_type_id' => $this->contactFieldEmailId(), ]); } } } /** * @param Contact $contact * @return void */ public function importTel(\App\Contact $contact): void { if (is_null($this->currentEntry->TEL)) { return; } foreach ($this->currentEntry->TEL as $tel) { ContactField::firstOrCreate([ 'account_id' => $contact->account_id, 'contact_id' => $contact->id, 'data' => $this->formatValue($tel), 'contact_field_type_id' => $this->contactFieldPhoneId(), ]); } } private function contactFieldEmailId() { if (! $this->contactFieldEmailId) { $contactFieldType = \App\ContactFieldType::where('type', 'email')->first(); $this->contactFieldEmailId = $contactFieldType->id; } return $this->contactFieldEmailId; } private function contactFieldPhoneId() { if (! $this->contactFieldPhoneId) { $contactFieldType = \App\ContactFieldType::where('type', 'phone')->first(); $this->contactFieldPhoneId = $contactFieldType->id; } return $this->contactFieldPhoneId; } }