app/Http/Controllers/ContactsController.php (482 lines of code) (raw):

<?php namespace App\Http\Controllers; use Illuminate\View\View; use App\Helpers\DateHelper; use App\Helpers\FormHelper; use App\Models\Contact\Tag; use Illuminate\Support\Str; use Illuminate\Http\Request; use App\Helpers\GenderHelper; use App\Helpers\LocaleHelper; use App\Helpers\SearchHelper; use App\Helpers\AccountHelper; use App\Helpers\StorageHelper; use App\Models\Contact\Contact; use App\Services\VCard\ExportVCard; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Auth; use App\Jobs\UpdateLastConsultedDate; use Illuminate\Http\RedirectResponse; use Illuminate\Contracts\View\Factory; use App\Models\Relationship\Relationship; use Barryvdh\Debugbar\Facade as Debugbar; use App\Services\User\UpdateViewPreference; use Illuminate\Validation\ValidationException; use App\Services\Contact\Contact\CreateContact; use App\Services\Contact\Contact\UpdateContact; use App\Services\Contact\Contact\DestroyContact; use App\Services\Contact\Contact\UpdateWorkInformation; use App\Services\Contact\Contact\UpdateContactFoodPreferences; use App\Http\Resources\Contact\ContactSearch as ContactResource; class ContactsController extends Controller { /** * Display a listing of the resource. * * @param Request $request * * @return View|RedirectResponse */ public function index(Request $request) { return $this->contacts($request, true); } /** * Display a listing of the resource. * * @param Request $request * * @return View|RedirectResponse */ public function archived(Request $request) { return $this->contacts($request, false); } /** * Display contacts. * * @param Request $request * @param bool $active * @return View|RedirectResponse */ private function contacts(Request $request, bool $active) { $user = $request->user(); $sort = $request->input('sort') ?? $user->contacts_sort_order; $showDeceased = $request->input('show_dead'); if ($user->contacts_sort_order !== $sort) { app(UpdateViewPreference::class)->execute([ 'account_id' => $user->account_id, 'user_id' => $user->id, 'preference' => $sort, ]); } $contacts = $user->account->contacts()->real(); if ($active) { $archived = (clone $contacts)->notActive(); $contacts = (clone $contacts)->active(); $nbArchived = $archived->count(); } else { $contacts = $contacts->notActive(); $nbArchived = $contacts->count(); } $tags = null; $url = ''; $count = 1; if ($request->input('tag1')) { // get contacts with selected tags $tags = collect(); while ($request->input('tag'.$count)) { $tag = Tag::where([ 'account_id' => auth()->user()->account_id, 'name_slug' => $request->input('tag'.$count), ]); if ($tag->count() > 0) { $tag = $tag->get(); if (! $tags->contains($tag[0])) { $tags = $tags->concat($tag); } $url .= 'tag'.$count.'='.$tag[0]->name_slug.'&'; } $count++; } if ($tags->count() === 0) { return redirect()->route('people.index'); } else { $contacts = $contacts->tags($tags); } } elseif ($request->input('no_tag')) { $contacts = $contacts->tags('NONE'); } $contactsCount = (clone $contacts)->alive()->count(); $deceasedCount = (clone $contacts)->dead()->count(); if ($showDeceased === 'true') { $contactsCount += $deceasedCount; } $accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account); return view('people.index') ->withAccountHasLimitations($accountHasLimitations) ->with('hidingDeceased', $showDeceased != 'true') ->with('deceasedCount', $deceasedCount) ->withActive($active) ->withContactsCount($contactsCount) ->withHasArchived($nbArchived > 0) ->withArchivedContacts($nbArchived) ->withTags($tags) ->withTagsCount(Tag::contactsCount()) ->withUrl($url) ->withTagCount($count) ->withTagLess($request->input('no_tag') ?? false); } /** * Show the form to add a new contact. * * @param Request $request * @return View|Factory|RedirectResponse */ public function create(Request $request) { return $this->createForm($request, false); } /** * Show the form in case the contact is missing. * * @param Request $request * @return View|Factory|RedirectResponse */ public function missing(Request $request) { return $this->createForm($request, true); } /** * Show the Add user form unless the contact has limitations. * * @param Request $request * @param bool $isContactMissing * @return View|Factory|RedirectResponse */ private function createForm(Request $request, bool $isContactMissing = false) { if (AccountHelper::hasReachedContactLimit(auth()->user()->account) && AccountHelper::hasLimitations(auth()->user()->account) && ! auth()->user()->account->legacy_free_plan_unlimited_contacts) { return redirect()->route('settings.subscriptions.index'); } $accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account); return view('people.create') ->withAccountHasLimitations($accountHasLimitations) ->withIsContactMissing($isContactMissing) ->withGenders(GenderHelper::getGendersInput()) ->withDefaultGender(auth()->user()->account->default_gender_id) ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user())) ->withFirstName($request->input('first_name')) ->withLastName($request->input('last_name')); } /** * Store the contact. * * @param Request $request * @return RedirectResponse */ public function store(Request $request) { try { $contact = app(CreateContact::class)->execute([ 'account_id' => auth()->user()->account_id, 'author_id' => auth()->user()->id, 'first_name' => $request->input('first_name'), 'middle_name' => $request->input('middle_name', null), 'last_name' => $request->input('last_name', null), 'nickname' => $request->input('nickname', null), 'gender_id' => $request->input('gender'), 'is_birthdate_known' => false, 'is_deceased' => false, 'is_deceased_date_known' => false, ]); } catch (ValidationException $e) { return back() ->withInput() ->withErrors($e->validator); } // Did the user press "Save" or "Submit and add another person" if (! is_null($request->input('save'))) { return redirect()->route('people.show', $contact); } else { return redirect()->route('people.create') ->with('status', trans('people.people_add_success', ['name' => $contact->name])); } } /** * Display the contact profile. * * @param Contact $contact * * @return View|RedirectResponse */ public function show(Contact $contact) { // make sure we don't display a partial contact if ($contact->is_partial) { $realContact = $contact->getRelatedRealContact(); if (is_null($realContact)) { return redirect()->route('people.index') ->withErrors(trans('people.people_not_found')); } return redirect()->route('people.show', $realContact); } $contact->load(['notes' => function ($query) { $query->orderBy('updated_at', 'desc'); }]); UpdateLastConsultedDate::dispatch($contact); $relationships = $contact->relationships; // get love relationship type $loveRelationships = $relationships->filter(function ($item) { return $item->relationshipType->relationshipTypeGroup->name == 'love'; }); // get family relationship type $familyRelationships = $relationships->filter(function ($item) { return $item->relationshipType->relationshipTypeGroup->name == 'family'; }); // get friend relationship type $friendRelationships = $relationships->filter(function ($item) { return $item->relationshipType->relationshipTypeGroup->name == 'friend'; }); // get work relationship type $workRelationships = $relationships->filter(function ($item) { return $item->relationshipType->relationshipTypeGroup->name == 'work'; }); // reminders $reminders = $contact->activeReminders; $relevantRemindersFromRelatedContacts = $contact->getBirthdayRemindersAboutRelatedContacts(); $reminders = $reminders->merge($relevantRemindersFromRelatedContacts); // now we need to sort the reminders by next date they will be triggered foreach ($reminders as $reminder) { $next_expected_date = $reminder->calculateNextExpectedDateOnTimezone(); $reminder->next_expected_date_human_readable = DateHelper::getShortDate($next_expected_date); $reminder->next_expected_date = DateHelper::getDate($next_expected_date); } $reminders = $reminders->sortBy('next_expected_date'); // list of active features $modules = $contact->account->modules()->active()->get(); // add `---` at the top of the dropdowns $days = DateHelper::getListOfDays(); $days->prepend([ 'id' => 0, 'name' => '---', ]); $months = DateHelper::getListOfMonths(); $months->prepend([ 'id' => 0, 'name' => '---', ]); $hasReachedAccountStorageLimit = StorageHelper::hasReachedAccountStorageLimit($contact->account); $accountHasLimitations = AccountHelper::hasLimitations($contact->account); return view('people.profile') ->withHasReachedAccountStorageLimit($hasReachedAccountStorageLimit) ->withAccountHasLimitations($accountHasLimitations) ->withLoveRelationships($loveRelationships) ->withFamilyRelationships($familyRelationships) ->withFriendRelationships($friendRelationships) ->withWorkRelationships($workRelationships) ->withReminders($reminders) ->withModules($modules) ->withContact($contact) ->withWeather($contact->getWeather()) ->withDays($days) ->withMonths($months) ->withYears(DateHelper::getListOfYears()); } /** * Display the Edit people's view. * * @param Contact $contact * * @return View */ public function edit(Contact $contact) { $now = now(); $age = (string) (! is_null($contact->birthdate) ? $contact->birthdate->getAge() : 0); $birthdate = ! is_null($contact->birthdate) ? $contact->birthdate->date->toDateString() : $now->toDateString(); $deceaseddate = ! is_null($contact->deceasedDate) ? $contact->deceasedDate->date->toDateString() : ''; $day = ! is_null($contact->birthdate) ? $contact->birthdate->date->day : $now->day; $month = ! is_null($contact->birthdate) ? $contact->birthdate->date->month : $now->month; $hasBirthdayReminder = ! is_null($contact->birthday_reminder_id); $hasDeceasedReminder = ! is_null($contact->deceased_reminder_id); $accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account); return view('people.edit') ->withAccountHasLimitations($accountHasLimitations) ->withContact($contact) ->withDays(DateHelper::getListOfDays()) ->withMonths(DateHelper::getListOfMonths()) ->withBirthdayState($contact->getBirthdayState()) ->withBirthdate($birthdate) ->withDeceaseddate($deceaseddate) ->withDay($day) ->withMonth($month) ->withAge($age) ->withHasBirthdayReminder($hasBirthdayReminder) ->withHasDeceasedReminder($hasDeceasedReminder) ->withGenders(GenderHelper::getGendersInput()) ->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user())); } /** * Update the contact. * * @param Request $request * @param Contact $contact * * @return RedirectResponse */ public function update(Request $request, Contact $contact) { // process birthday dates // TODO: remove this part entirely when we redo this whole SpecialDate // thing if ($request->input('birthdate') == 'exact') { $birthdate = $request->input('birthdayDate'); $birthdate = DateHelper::parseDate($birthdate); $day = $birthdate->day; $month = $birthdate->month; $year = $birthdate->year; } else { $day = $request->input('day'); $month = $request->input('month'); $year = $request->input('year'); } $is_deceased_date_known = false; if ($request->input('is_deceased_date_known') === 'true' && $request->input('deceased_date')) { $is_deceased_date_known = true; $deceased_date = $request->input('deceased_date'); $deceased_date = DateHelper::parseDate($deceased_date); $deceased_date_day = $deceased_date->day; $deceased_date_month = $deceased_date->month; $deceased_date_year = $deceased_date->year; } else { $deceased_date_day = $deceased_date_month = $deceased_date_year = null; } if (! empty($request->input('is_deceased'))) { //if the contact has died, disable StayInTouch $contact->updateStayInTouchFrequency(0); $contact->setStayInTouchTriggerDate(0); } $data = [ 'account_id' => auth()->user()->account_id, 'contact_id' => $contact->id, 'first_name' => $request->input('firstname'), 'middle_name' => $request->input('middlename', null), 'last_name' => $request->input('lastname', null), 'nickname' => $request->input('nickname', null), 'gender_id' => $request->input('gender'), 'description' => $request->input('description', null), 'is_birthdate_known' => ! empty($request->input('birthdate')) && $request->input('birthdate') !== 'unknown', 'birthdate_day' => $day, 'birthdate_month' => $month, 'birthdate_year' => $year, 'birthdate_is_age_based' => $request->input('birthdate') === 'approximate', 'birthdate_age' => $request->input('age'), 'birthdate_add_reminder' => ! empty($request->input('addReminder')), 'is_deceased' => ! empty($request->input('is_deceased')), 'is_deceased_date_known' => $is_deceased_date_known, 'deceased_date_day' => $deceased_date_day, 'deceased_date_month' => $deceased_date_month, 'deceased_date_year' => $deceased_date_year, 'deceased_date_add_reminder' => ! empty($request->input('add_reminder_deceased')), ]; $contact = app(UpdateContact::class)->execute($data); if ($request->file('avatar') != '') { if ($contact->has_avatar) { try { $contact->deleteAvatars(); } catch (\Exception $e) { Log::warning(__CLASS__.' update: Failed to delete avatars', [ 'contact' => $contact, 'exception' => $e, ]); } } $contact->has_avatar = true; $contact->avatar_location = config('filesystems.default'); $contact->avatar_file_name = $request->avatar->storePublicly('avatars', $contact->avatar_location); $contact->save(); } return redirect()->route('people.show', $contact) ->with('success', trans('people.information_edit_success')); } /** * Delete the contact. * * @param Request $request * @param Contact $contact * * @return RedirectResponse */ public function destroy(Request $request, Contact $contact) { if ($contact->account_id != auth()->user()->account_id) { return redirect()->route('people.index'); } $data = [ 'account_id' => auth()->user()->account_id, 'contact_id' => $contact->id, ]; app(DestroyContact::class)->execute($data); return redirect()->route('people.index') ->with('success', trans('people.people_delete_success')); } /** * Show the Edit work view. * * @param Request $request * @param Contact $contact * * @return View */ public function editWork(Request $request, Contact $contact) { return view('people.work.edit') ->withContact($contact); } /** * Save the work information. * * @param Request $request * @param Contact $contact * * @return RedirectResponse */ public function updateWork(Request $request, Contact $contact) { $contact = app(UpdateWorkInformation::class)->execute([ 'account_id' => auth()->user()->account_id, 'author_id' => auth()->user()->id, 'contact_id' => $contact->id, 'job' => $request->input('job'), 'company' => $request->input('company'), ]); return redirect()->route('people.show', $contact) ->with('success', trans('people.work_edit_success')); } /** * Show the Edit food preferences view. * * @param Request $request * @param Contact $contact * * @return View */ public function editFoodPreferences(Request $request, Contact $contact) { $accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account); return view('people.food-preferences.edit') ->withAccountHasLimitations($accountHasLimitations) ->withContact($contact); } /** * Save the food preferences. * * @param Request $request * @param Contact $contact * * @return RedirectResponse */ public function updateFoodPreferences(Request $request, Contact $contact) { $contact = app(UpdateContactFoodPreferences::class)->execute([ 'account_id' => auth()->user()->account_id, 'contact_id' => $contact->id, 'food_preferences' => $request->input('food'), ]); return redirect()->route('people.show', $contact) ->with('success', trans('people.food_preferences_add_success')); } /** * Search used in the header. * @param Request $request */ public function search(Request $request) { $needle = $request->needle; if ($needle == null) { return; } $results = SearchHelper::searchContacts($needle, 'created_at') ->paginate(20); if ($results->total() > 0) { return ContactResource::collection($results); } else { return ['noResults' => trans('people.people_search_no_results')]; } } /** * Download the contact as vCard. * @param Contact $contact * @return \Illuminate\Http\Response */ public function vCard(Contact $contact) { if (config('app.debug')) { Debugbar::disable(); } $vcard = app(ExportVCard::class)->execute([ 'account_id' => auth()->user()->account_id, 'contact_id' => $contact->id, ]); return response($vcard->serialize()) ->header('Content-type', 'text/x-vcard') ->header('Content-Disposition', 'attachment; filename='.Str::slug($contact->name, '-', LocaleHelper::getLang()).'.vcf'); } /** * Set or change the frequency of which the user wants to stay in touch with * the given contact. * * @param Request $request * @param Contact $contact * @return int */ public function stayInTouch(Request $request, Contact $contact) { $frequency = intval($request->input('frequency')); $state = $request->input('state'); if (AccountHelper::hasLimitations(auth()->user()->account)) { throw new \LogicException(trans('people.stay_in_touch_premium')); } // if not active, set frequency to 0 if (! $state) { $frequency = 0; } $result = $contact->updateStayInTouchFrequency($frequency); if (! $result) { throw new \LogicException(trans('people.stay_in_touch_invalid')); } $contact->setStayInTouchTriggerDate($frequency); return $frequency; } /** * Toggle favorites of a contact. * @param Request $request * @param Contact $contact * @return array */ public function favorite(Request $request, Contact $contact) { $bool = (bool) $request->input('toggle'); $contact->is_starred = $bool; $contact->save(); return [ 'is_starred' => $bool, ]; } /** * Toggle archive state of a contact. * * @param Request $request * @param Contact $contact * @return array */ public function archive(Request $request, Contact $contact) { $contact->is_active = ! $contact->is_active; $contact->save(); return [ 'is_active' => $contact->is_active, ]; } /** * Display the list of contacts. * * @param Request $request * @return array */ public function list(Request $request) { $accountId = auth()->user()->account_id; $user = $request->user(); $sort = $request->input('sort') ?? $user->contacts_sort_order; if ($user->contacts_sort_order !== $sort) { app(UpdateViewPreference::class)->execute([ 'account_id' => $user->account_id, 'user_id' => $user->id, 'preference' => $sort, ]); } $tags = null; $url = ''; $count = 1; $contacts = $user->account->contacts()->real(); // filter out archived contacts if necessary if ($request->input('show_archived') != 'true') { $contacts = $contacts->active(); } else { $contacts = $contacts->notActive(); } // filter out deceased if necessary if ($request->input('show_dead') != 'true') { $contacts = $contacts->alive(); } if ($request->input('no_tag')) { // get tag less contacts $contacts = $contacts->tags('NONE'); } elseif ($request->input('tag1')) { // get contacts with selected tags $tags = collect(); while ($request->input('tag'.$count)) { $tag = Tag::where([ 'account_id' => $accountId, 'name_slug' => $request->input('tag'.$count), ])->get(); if (! ($tags->contains($tag[0]))) { $tags = $tags->concat($tag); } $url = $url.'tag'.$count.'='.$tag[0]->name_slug.'&'; $count++; } if ($tags->count() > 0) { $contacts = $contacts->tags($tags); } } // get the number of contacts per page $perPage = $request->has('perPage') ? $request->input('perPage') : config('monica.number_of_contacts_pagination'); // search contacts $contacts = $contacts->search($request->input('search') ?? '', $accountId, 'is_starred', 'desc', $sort) ->paginate($perPage); return [ 'totalRecords' => $contacts->total(), 'contacts' => ContactResource::collection($contacts), ]; } }