'required|string|max:255', 'name' => timebank_config('rules.profile_user.name'), 'email' => timebank_config('rules.profile_user.email'), 'password' => timebank_config('rules.profile_user.password'), 'country' => 'required_if:validateCountry,true|integer', 'division' => 'required_if:validateDivision,true', 'city' => 'required_if:validateCity,true', 'district' => 'sometimes', 'ageConfirmed' => 'accepted', // 'captcha' => 'hiddencaptcha:3,300', // min, max time in sec for submitting form without captcha validation error ]; } public function messages() { return [ 'captcha.hiddencaptcha' => __('Automatic form completion detected. Try again filling in the form manually. Robots are not allowed to register.'), 'ageConfirmed.accepted' => __('You must confirm that you meet the minimum age requirement to register.'), ]; } public function mount(Request $request) { if (App::environment(['local'])) { // $ip = '103.75.231.255'; // Static IP address Brussels for testing $ip = '31.20.250.12'; // Static IP address The Hague for testing //$ip = '101.33.29.255'; // Static IP address in Amsterdam for testing //$ip = '102.129.156.0'; // Static IP address Berlin for testing } else { // TODO: Test ip lookup in production $ip = $request->ip(); // Dynamic IP address } $IpLocationInfo = IpLocation::get($ip); if ($IpLocationInfo) { $country = Country::select('id')->where('code', $IpLocationInfo->countryCode)->first(); if ($country) { $this->country = $country->id; } $division = DB::table('division_locales')->select('division_id')->where('name', 'LIKE', $IpLocationInfo->regionName)->where('locale', app()->getLocale())->first(); //We only need the city_id, therefore we use the default app locale in the where query. if ($division) { $this->division = $division->division_id; } $city = DB::table('city_locales')->select('city_id')->where('name', $IpLocationInfo->cityName)->where('locale', app()->getLocale())->first(); //We only need the city_id, therefore we use the default app locale in the where query. if ($city) { $this->city = $city->city_id; }; } $this->setValidationOptions(); // Failsafe: If no published principles post exists, bypass the principles modal $principlesPost = $this->getPrinciplesPost(); if (!$principlesPost) { $this->showPrinciplesModal = false; $this->principlesAccepted = true; // principlesData remains null, user will be registered without principles acceptance } } public function emitLocationToChildren() { $this->dispatch('countryToChildren', $this->country); $this->dispatch('divisionToChildren', $this->division); $this->dispatch('cityToChildren', $this->city); $this->dispatch('districtToChildren', $this->district); } public function countryToParent($value) { $this->country = $value; $this->setValidationOptions(); } public function divisionToParent($value) { $this->division = $value; $this->setValidationOptions(); } public function cityToParent($value) { $this->city = $value; $this->setValidationOptions(); } public function districtToParent($value) { $this->district = $value; $this->setValidationOptions(); } public function updated($field) { $this->validateOnly($field); } public function setValidationOptions() { $this->validateCountry = $this->validateDivision = $this->validateCity = true; // In case no cities or divisions for selected country are seeded in database if ($this->country) { $countDivisions = Country::find($this->country)->divisions->count(); $countCities = Country::find($this->country)->cities->count(); if ($countDivisions > 0 && $countCities < 1) { $this->validateDivision = true; $this->validateCity = false; } elseif ($countDivisions < 1 && $countCities > 1) { $this->validateDivision = false; $this->validateCity = true; } elseif ($countDivisions < 1 && $countCities < 1) { $this->validateDivision = false; $this->validateCity = false; } elseif ($countDivisions > 0 && $countCities > 0) { $this->validateDivision = false; $this->validateCity = true; } } // In case no country is selected, no need to show other validation errors if (!$this->country) { $this->validateCountry = true; $this->validateDivision = $this->validateCity = false; } } /** * Get the current principles post for the current locale * * @return Post|null */ protected function getPrinciplesPost() { $locale = app()->getLocale(); return Post::with(['translations' => function ($query) use ($locale) { $query->where('locale', 'like', $locale . '%') ->whereDate('from', '<=', now()) ->where(function ($query) { $query->whereDate('till', '>', now())->orWhereNull('till'); }) ->orderBy('updated_at', 'desc') ->limit(1); }]) ->whereHas('category', function ($query) { $query->where('type', 'SiteContents\\Static\\Principles'); }) ->whereHas('translations', function ($query) use ($locale) { $query->where('locale', 'like', $locale . '%') ->whereDate('from', '<=', now()) ->where(function ($query) { $query->whereDate('till', '>', now())->orWhereNull('till'); }); }) ->first(); } /** * Accept the principles and store metadata for later user creation * * @return void */ public function acceptPrinciples() { // Get the current principles post $principlesPost = $this->getPrinciplesPost(); if (!$principlesPost || !$principlesPost->translations->first()) { $this->notification()->error( $title = __('Error'), $description = __('Unable to find the current principles document.') ); return; } $translation = $principlesPost->translations->first(); // Store acceptance metadata for later user creation $this->principlesData = [ 'post_id' => $principlesPost->id, 'post_translation_id' => $translation->id, 'locale' => $translation->locale, 'from' => $translation->from, 'updated_at' => $translation->updated_at->toDateTimeString(), 'accepted_at' => now()->toDateTimeString(), ]; $this->principlesAccepted = true; $this->showPrinciplesModal = false; $this->notification()->success( $title = __('Thank you'), $description = __('You can now proceed with registration.') ); } public function create($input = null) { $this->waitMessage = true; $valid = $this->validate(); try { // Use a transaction for creating the new user DB::transaction(function () use ($valid): void { $user = User::create([ 'full_name' => $valid['full_name'], 'name' => $valid['name'], 'email' => $valid['email'], 'password' => Hash::make($valid['password']), 'profile_photo_path' => timebank_config('profiles.user.profile_photo_path_new'), 'lang_preference' => app()->getLocale(), // App locale is set by mcamara/laravel-localization package: set app locale according to browser language 'limit_min' => timebank_config('profiles.user.limit_min'), 'limit_max' => timebank_config('profiles.user.limit_max'), 'principles_terms_accepted' => $this->principlesData, ]); $location = new Location(); $location->name = __('Default location'); $location->country_id = $valid['country']; $location->division_id = $valid['division']; $location->city_id = $valid['city']; $location->district_id = $valid['district']; $user->locations()->save($location); // save the new location for the user $account = new Account(); $account->name = __(timebank_config('accounts.user.name')); $account->limit_min = timebank_config('accounts.user.limit_min'); $account->limit_max = timebank_config('accounts.user.limit_max'); // TODO: remove testing comment for production // Uncomment to test a failed transaction // Simulate an error by throwing an exception // throw new \Exception('Simulated error before saving account'); $user->accounts()->save($account); // create the new account for the user // TODO: Replace commented rtippin messenger logic with wirechat logic // Attach user to Messenger as a provider // Messenger::getProviderMessenger($user); // WireUI notification $this->notification()->success( $title = __('Your registration is saved!'), ); $this->reset(); Auth::guard('web')->login($user); event(new Registered($user)); }); // End of transaction $this->waitMessage = false; return redirect()->route('verification.notice'); } catch (Throwable $e) { $this->waitMessage = false; // WireUI notification $this->notification()->send([ 'title' => __('Registration failed') . '! ', 'description' => __('Sorry, your data could not be saved!') . '

' . __('Our team has been notified. Please try again later.') . '

' . __('Error') . ': ' . $e->getMessage(), 'icon' => 'error', 'timeout' => 100000 ]); $warningMessage = 'User registration failed'; $error = $e; $eventTime = now()->toDateTimeString(); $ip = request()->ip(); $ipLocationInfo = IpLocation::get($ip); // Escape ipLocation errors when not in production if (!$ipLocationInfo || App::environment(['local', 'development', 'staging'])) { $ipLocationInfo = (object) [ 'cityName' => 'local City', 'regionName' => 'local Region', 'countryName' => 'local Country', ]; } $lang_preference = app()->getLocale(); $country = DB::table('country_locales')->where('country_id', $valid['country'])->where('locale', timebank_config('base_language'))->value('name'); $division = DB::table('division_locales')->where('division_id', $valid['division'])->where('locale', timebank_config('base_language'))->value('name'); $city = DB::table('city_locales')->where('city_id', $valid['city'])->where('locale', timebank_config('base_language'))->value('name'); $district = DB::table('district_locales')->where('district_id', $valid['district'])->where('locale', timebank_config('base_language'))->value('name'); // Log this event and mail to admin Log::warning($warningMessage, [ 'full_name' => $valid['full_name'], 'name' => $valid['name'], 'email' => $valid['email'], 'lang_preference' => $lang_preference, 'country' => $country, 'division' => $division, 'city' => $city, 'district' => $district, 'IP address' => $ip, 'IP location' => $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName, 'Event Time' => $eventTime, 'Message' => $error, ]); Mail::raw( $warningMessage . '.' . "\n\n" . 'Full name: ' . $valid['full_name'] . "\n" . 'Name: ' . $valid['name'] . "\n" . 'Email: ' . $valid['email'] . "\n" . 'Language preference: ' . $lang_preference . "\n" . 'Country: ' . $country . "\n" . 'Division: ' . $division . "\n" . 'City: ' . $city . "\n" . 'District: ' . $district . "\n" . 'IP address: ' . $ip . "\n" . 'IP location: ' . $ipLocationInfo->cityName . ', ' . $ipLocationInfo->regionName . ', ' . $ipLocationInfo->countryName . "\n" . 'Event Time: ' . $eventTime . "\n\n" . $error, function ($message) use ($warningMessage) { $message->to(timebank_config('mail.system_admin.email'))->subject($warningMessage); }, ); return back(); } } public function render() { $principlesPost = $this->getPrinciplesPost(); return view('livewire.registration', [ 'principlesPost' => $principlesPost, ]); } }