diff --git a/src/app/Http/Controllers/API/AuthController.php b/src/app/Http/Controllers/API/AuthController.php index 85cc4f15..7132716b 100644 --- a/src/app/Http/Controllers/API/AuthController.php +++ b/src/app/Http/Controllers/API/AuthController.php @@ -1,197 +1,197 @@ user(); if (!empty(request()->input('refresh'))) { return $this->refreshAndRespond(request(), $user); } $response = V4\UsersController::userResponse($user); return response()->json($response); } /** * Helper method for other controllers with user auto-logon * functionality * * @param \App\User $user User model object * @param string $password Plain text password * @param string|null $secondFactor Second factor code if available */ public static function logonResponse(User $user, string $password, string $secondFactor = null) { $proxyRequest = Request::create('/oauth/token', 'POST', [ 'username' => $user->email, 'password' => $password, 'grant_type' => 'password', 'client_id' => \config('auth.proxy.client_id'), 'client_secret' => \config('auth.proxy.client_secret'), 'scope' => 'api', 'secondfactor' => $secondFactor ]); $proxyRequest->headers->set('X-Client-IP', request()->ip()); $tokenResponse = app()->handle($proxyRequest); return self::respondWithToken($tokenResponse, $user); } /** * Get an oauth token via given credentials. * * @param \Illuminate\Http\Request $request The API request. * * @return \Illuminate\Http\JsonResponse */ public function login(Request $request) { $v = Validator::make( $request->all(), [ 'email' => 'required|min:3', 'password' => 'required|min:1', ] ); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } $user = \App\User::where('email', $request->email)->first(); if (!$user) { - return response()->json(['status' => 'error', 'message' => \trans('auth.failed')], 401); + return response()->json(['status' => 'error', 'message' => self::trans('auth.failed')], 401); } return self::logonResponse($user, $request->password, $request->secondfactor); } /** * Get the user (geo) location * * @return \Illuminate\Http\JsonResponse */ public function location() { $ip = request()->ip(); $response = [ 'ipAddress' => $ip, 'countryCode' => \App\Utils::countryForIP($ip, ''), ]; return response()->json($response); } /** * Log the user out (Invalidate the token) * * @return \Illuminate\Http\JsonResponse */ public function logout() { $tokenId = Auth::user()->token()->id; $tokenRepository = app(TokenRepository::class); $refreshTokenRepository = app(RefreshTokenRepository::class); // Revoke an access token... $tokenRepository->revokeAccessToken($tokenId); // Revoke all of the token's refresh tokens... $refreshTokenRepository->revokeRefreshTokensByAccessTokenId($tokenId); return response()->json([ 'status' => 'success', - 'message' => \trans('auth.logoutsuccess') + 'message' => self::trans('auth.logoutsuccess') ]); } /** * Refresh a token. * * @return \Illuminate\Http\JsonResponse */ public function refresh(Request $request) { return self::refreshAndRespond($request); } /** * Refresh the token and respond with it. * * @param \Illuminate\Http\Request $request The API request. * @param ?\App\User $user The user being authenticated * * @return \Illuminate\Http\JsonResponse */ protected static function refreshAndRespond(Request $request, $user = null) { $proxyRequest = Request::create('/oauth/token', 'POST', [ 'grant_type' => 'refresh_token', 'refresh_token' => $request->refresh_token, 'client_id' => \config('auth.proxy.client_id'), 'client_secret' => \config('auth.proxy.client_secret'), ]); $tokenResponse = app()->handle($proxyRequest); return self::respondWithToken($tokenResponse, $user); } /** * Get the token array structure. * * @param \Illuminate\Http\JsonResponse $tokenResponse The response containing the token. * @param ?\App\User $user The user being authenticated * * @return \Illuminate\Http\JsonResponse */ protected static function respondWithToken($tokenResponse, $user = null) { $data = json_decode($tokenResponse->getContent()); if ($tokenResponse->getStatusCode() != 200) { if (isset($data->error) && $data->error == 'secondfactor' && isset($data->error_description)) { $errors = ['secondfactor' => $data->error_description]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } - return response()->json(['status' => 'error', 'message' => \trans('auth.failed')], 401); + return response()->json(['status' => 'error', 'message' => self::trans('auth.failed')], 401); } if ($user) { $response = V4\UsersController::userResponse($user); } else { $response = []; } $response['status'] = 'success'; $response['access_token'] = $data->access_token; $response['refresh_token'] = $data->refresh_token; $response['token_type'] = 'bearer'; $response['expires_in'] = $data->expires_in; return response()->json($response); } } diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php index 864c25de..2fa48228 100644 --- a/src/app/Http/Controllers/API/PasswordResetController.php +++ b/src/app/Http/Controllers/API/PasswordResetController.php @@ -1,211 +1,211 @@ all(), ['email' => 'required|email']); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Find a user by email $user = User::findByEmail($request->email); if (!$user) { - $errors = ['email' => \trans('validation.usernotexists')]; + $errors = ['email' => self::trans('validation.usernotexists')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } if (!$user->getSetting('external_email')) { - $errors = ['email' => \trans('validation.noextemail')]; + $errors = ['email' => self::trans('validation.noextemail')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } // Geo-lockin check if (!$user->validateLocation($request->ip())) { // FIXME: Or maybe we should just throw some more generic error response/code? - $errors = ['email' => \trans('validation.geolockinerror')]; + $errors = ['email' => self::trans('validation.geolockinerror')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } // Generate the verification code $code = new VerificationCode(['mode' => 'password-reset']); $user->verificationcodes()->save($code); // Send email/sms message PasswordResetEmail::dispatch($code); return response()->json(['status' => 'success', 'code' => $code->code]); } /** * Validation of the verification code. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function verify(Request $request) { // Validate the request args $v = Validator::make( $request->all(), [ 'code' => 'required', 'short_code' => 'required', ] ); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Validate the verification code $code = VerificationCode::where('code', $request->code)->where('active', true)->first(); if ( empty($code) || $code->isExpired() || $code->mode !== 'password-reset' || Str::upper($request->short_code) !== Str::upper($code->short_code) ) { $errors = ['short_code' => "The code is invalid or expired."]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } // For last-step remember the code object, so we can delete it // with single SQL query (->delete()) instead of two (::destroy()) $request->code = $code; return response()->json([ 'status' => 'success', // we need user's ID for e.g. password policy checks 'userId' => $code->user_id, ]); } /** * Password change * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function reset(Request $request) { $v = $this->verify($request); if ($v->status() !== 200) { return $v; } $user = $request->code->user; // Validate the password $v = Validator::make( $request->all(), ['password' => ['required', 'confirmed', new Password($user->walletOwner())]] ); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Change the user password $user->setPasswordAttribute($request->password); $user->save(); // Remove the verification code $request->code->delete(); return AuthController::logonResponse($user, $request->password); } /** * Create a verification code for the current user. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function codeCreate(Request $request) { // Generate the verification code $code = new VerificationCode(); $code->mode = 'password-reset'; // These codes are valid for 24 hours $code->expires_at = now()->addHours(24); // The code is inactive until it is submitted via a different endpoint $code->active = false; $this->guard()->user()->verificationcodes()->save($code); return response()->json([ 'status' => 'success', 'code' => $code->code, 'short_code' => $code->short_code, 'expires_at' => $code->expires_at->toDateTimeString(), ]); } /** * Delete a verification code. * * @param string $id Code identifier * * @return \Illuminate\Http\JsonResponse The response */ public function codeDelete($id) { // Accept - input if (strpos($id, '-')) { $id = explode('-', $id)[1]; } $code = VerificationCode::find($id); if (!$code) { return $this->errorResponse(404); } $current_user = $this->guard()->user(); if (empty($code->user) || !$current_user->canUpdate($code->user)) { return $this->errorResponse(403); } $code->delete(); return response()->json([ 'status' => 'success', - 'message' => \trans('app.password-reset-code-delete-success'), + 'message' => self::trans('app.password-reset-code-delete-success'), ]); } } diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php index d44825b8..2a8558bf 100644 --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -1,596 +1,596 @@ orderBy('months')->orderByDesc('title')->get() ->map(function ($plan) { $button = self::trans("app.planbutton-{$plan->title}"); if (strpos($button, 'app.planbutton') !== false) { $button = self::trans('app.planbutton', ['plan' => $plan->name]); } return [ 'title' => $plan->title, 'name' => $plan->name, 'button' => $button, 'description' => $plan->description, 'mode' => $plan->mode ?: Plan::MODE_EMAIL, 'isDomain' => $plan->hasDomain(), ]; }) ->all(); return response()->json(['status' => 'success', 'plans' => $plans]); } /** * Returns list of public domains for signup. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function domains(Request $request) { return response()->json(['status' => 'success', 'domains' => Domain::getPublicDomains()]); } /** * Starts signup process. * * Verifies user name and email/phone, sends verification email/sms message. * Returns the verification code. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function init(Request $request) { $rules = [ 'first_name' => 'max:128', 'last_name' => 'max:128', 'voucher' => 'max:32', ]; $plan = $this->getPlan(); if ($plan->mode == Plan::MODE_TOKEN) { $rules['token'] = ['required', 'string', new SignupToken()]; } else { $rules['email'] = ['required', 'string', new SignupExternalEmail()]; } // Check required fields, validate input $v = Validator::make($request->all(), $rules); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()->toArray()], 422); } // Generate the verification code $code = SignupCode::create([ 'email' => $plan->mode == Plan::MODE_TOKEN ? $request->token : $request->email, 'first_name' => $request->first_name, 'last_name' => $request->last_name, 'plan' => $plan->title, 'voucher' => $request->voucher, ]); $response = [ 'status' => 'success', 'code' => $code->code, 'mode' => $plan->mode ?: 'email', ]; if ($plan->mode == Plan::MODE_TOKEN) { // Token verification, jump to the last step $has_domain = $plan->hasDomain(); $response['short_code'] = $code->short_code; $response['is_domain'] = $has_domain; $response['domains'] = $has_domain ? [] : Domain::getPublicDomains(); } else { // External email verification, send an email message SignupVerificationEmail::dispatch($code); } return response()->json($response); } /** * Returns signup invitation information. * * @param string $id Signup invitation identifier * * @return \Illuminate\Http\JsonResponse|void */ public function invitation($id) { $invitation = SignupInvitation::withEnvTenantContext()->find($id); if (empty($invitation) || $invitation->isCompleted()) { return $this->errorResponse(404); } $has_domain = $this->getPlan()->hasDomain(); $result = [ 'id' => $id, 'is_domain' => $has_domain, 'domains' => $has_domain ? [] : Domain::getPublicDomains(), ]; return response()->json($result); } /** * Validation of the verification code. * * @param \Illuminate\Http\Request $request HTTP request * @param bool $update Update the signup code record * * @return \Illuminate\Http\JsonResponse JSON response */ public function verify(Request $request, $update = true) { // Validate the request args $v = Validator::make( $request->all(), [ 'code' => 'required', 'short_code' => 'required', ] ); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } // Validate the verification code $code = SignupCode::find($request->code); if ( empty($code) || $code->isExpired() || Str::upper($request->short_code) !== Str::upper($code->short_code) ) { $errors = ['short_code' => "The code is invalid or expired."]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } // For signup last-step mode remember the code object, so we can delete it // with single SQL query (->delete()) instead of two $request->code = $code; if ($update) { $code->verify_ip_address = $request->ip(); $code->save(); } $has_domain = $this->getPlan()->hasDomain(); // Return user name and email/phone/voucher from the codes database, // domains list for selection and "plan type" flag return response()->json([ 'status' => 'success', 'email' => $code->email, 'first_name' => $code->first_name, 'last_name' => $code->last_name, 'voucher' => $code->voucher, 'is_domain' => $has_domain, 'domains' => $has_domain ? [] : Domain::getPublicDomains(), ]); } /** * Validates the input to the final signup request. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function signupValidate(Request $request) { // Validate input $v = Validator::make( $request->all(), [ 'login' => 'required|min:2', 'password' => ['required', 'confirmed', new Password()], 'domain' => 'required', 'voucher' => 'max:32', ] ); if ($v->fails()) { return response()->json(['status' => 'error', 'errors' => $v->errors()], 422); } $settings = []; // Plan parameter is required/allowed in mandate mode if (!empty($request->plan) && empty($request->code) && empty($request->invitation)) { $plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first(); if (!$plan || $plan->mode != Plan::MODE_MANDATE) { - $msg = \trans('validation.exists', ['attribute' => 'plan']); + $msg = self::trans('validation.exists', ['attribute' => 'plan']); return response()->json(['status' => 'error', 'errors' => ['plan' => $msg]], 422); } } elseif ($request->invitation) { // Signup via invitation $invitation = SignupInvitation::withEnvTenantContext()->find($request->invitation); if (empty($invitation) || $invitation->isCompleted()) { return $this->errorResponse(404); } // Check required fields $v = Validator::make( $request->all(), [ 'first_name' => 'max:128', 'last_name' => 'max:128', ] ); $errors = $v->fails() ? $v->errors()->toArray() : []; if (!empty($errors)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } $settings = [ 'external_email' => $invitation->email, 'first_name' => $request->first_name, 'last_name' => $request->last_name, ]; } else { // Validate verification codes (again) $v = $this->verify($request, false); if ($v->status() !== 200) { return $v; } $plan = $this->getPlan(); // Get user name/email from the verification code database $code_data = $v->getData(); $settings = [ 'first_name' => $code_data->first_name, 'last_name' => $code_data->last_name, ]; if ($plan->mode == Plan::MODE_TOKEN) { $settings['signup_token'] = $code_data->email; } else { $settings['external_email'] = $code_data->email; } } // Find the voucher discount if ($request->voucher) { $discount = Discount::where('code', \strtoupper($request->voucher)) ->where('active', true)->first(); if (!$discount) { - $errors = ['voucher' => \trans('validation.voucherinvalid')]; + $errors = ['voucher' => self::trans('validation.voucherinvalid')]; return response()->json(['status' => 'error', 'errors' => $errors], 422); } } if (empty($plan)) { $plan = $this->getPlan(); } $is_domain = $plan->hasDomain(); // Validate login if ($errors = self::validateLogin($request->login, $request->domain, $is_domain)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } // Set some properties for signup() method $request->settings = $settings; $request->plan = $plan; $request->discount = $discount ?? null; $request->invitation = $invitation ?? null; $result = []; if ($plan->mode == Plan::MODE_MANDATE) { $result = $this->mandateForPlan($plan, $request->discount); } return response()->json($result + ['status' => 'success']); } /** * Finishes the signup process by creating the user account. * * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ public function signup(Request $request) { $v = $this->signupValidate($request); if ($v->status() !== 200) { return $v; } $is_domain = $request->plan->hasDomain(); // We allow only ASCII, so we can safely lower-case the email address $login = Str::lower($request->login); $domain_name = Str::lower($request->domain); $domain = null; DB::beginTransaction(); // Create domain record if ($is_domain) { $domain = Domain::create([ 'namespace' => $domain_name, 'type' => Domain::TYPE_EXTERNAL, ]); } // Create user record $user = User::create([ 'email' => $login . '@' . $domain_name, 'password' => $request->password, 'status' => User::STATUS_RESTRICTED, ]); if ($request->discount) { $wallet = $user->wallets()->first(); $wallet->discount()->associate($request->discount); $wallet->save(); } $user->assignPlan($request->plan, $domain); // Save the external email and plan in user settings $user->setSettings($request->settings); // Update the invitation if ($request->invitation) { $request->invitation->status = SignupInvitation::STATUS_COMPLETED; $request->invitation->user_id = $user->id; $request->invitation->save(); } // Soft-delete the verification code, and store some more info with it if ($request->code) { $request->code->user_id = $user->id; $request->code->submit_ip_address = $request->ip(); $request->code->deleted_at = \now(); $request->code->timestamps = false; $request->code->save(); } DB::commit(); $response = AuthController::logonResponse($user, $request->password); if ($request->plan->mode == Plan::MODE_MANDATE) { $data = $response->getData(true); $data['checkout'] = $this->mandateForPlan($request->plan, $request->discount, $user); $response->setData($data); } return $response; } /** * Collects some content to display to the user before redirect to a checkout page. * Optionally creates a recurrent payment mandate for specified user/plan. */ protected function mandateForPlan(Plan $plan, Discount $discount = null, User $user = null): array { $result = []; $min = \App\Payment::MIN_AMOUNT; $planCost = $cost = $plan->cost() * $plan->months; $disc = 0; if ($discount) { $planCost = (int) ($planCost * (100 - $discount->discount) / 100); $disc = $cost - $planCost; } if ($planCost > $min) { $min = $planCost; } if ($user) { $wallet = $user->wallets()->first(); $wallet->setSettings([ 'mandate_amount' => sprintf('%.2f', round($min / 100, 2)), 'mandate_balance' => 0, ]); $mandate = [ 'currency' => $wallet->currency, 'description' => \App\Tenant::getConfig($user->tenant_id, 'app.name') . ' Auto-Payment Setup', 'methodId' => PaymentProvider::METHOD_CREDITCARD, 'redirectUrl' => Utils::serviceUrl('/payment/status', $user->tenant_id), ]; $provider = PaymentProvider::factory($wallet); $result = $provider->createMandate($wallet, $mandate); } $country = Utils::countryForRequest(); $period = $plan->months == 12 ? 'yearly' : 'monthly'; $currency = \config('app.currency'); $rate = VatRate::where('country', $country) ->where('start', '<=', now()->format('Y-m-d h:i:s')) ->orderByDesc('start') ->limit(1) ->first(); $summary = '' . '' . self::trans("app.signup-subscription-{$period}") . '' . '' . Utils::money($cost, $currency) . '' . ''; if ($discount) { $summary .= '' . '' . self::trans('app.discount-code', ['code' => $discount->code]) . '' . '' . Utils::money(-$disc, $currency) . '' . ''; } $summary .= '' . '' . '' . self::trans('app.total') . '' . '' . Utils::money($planCost, $currency) . '' . ''; if ($rate && $rate->rate > 0) { // TODO: app.vat.mode $vat = round($planCost * $rate->rate / 100); $content = self::trans('app.vat-incl', [ 'rate' => Utils::percent($rate->rate), 'cost' => Utils::money($planCost - $vat, $currency), 'vat' => Utils::money($vat, $currency), ]); $summary .= '*' . $content . ''; } $trialEnd = $plan->free_months ? now()->copy()->addMonthsWithoutOverflow($plan->free_months) : now(); $params = [ 'cost' => Utils::money($planCost, $currency), 'date' => $trialEnd->toDateString(), ]; $result['title'] = self::trans("app.signup-plan-{$period}"); $result['content'] = self::trans('app.signup-account-mandate', $params); $result['summary'] = '' . $summary . '
'; return $result; } /** * Returns plan for the signup process * * @returns \App\Plan Plan object selected for current signup process */ protected function getPlan() { $request = request(); if (!$request->plan || !$request->plan instanceof Plan) { // Get the plan if specified and exists... if (($request->code instanceof SignupCode) && $request->code->plan) { $plan = Plan::withEnvTenantContext()->where('title', $request->code->plan)->first(); } elseif ($request->plan) { $plan = Plan::withEnvTenantContext()->where('title', $request->plan)->first(); } // ...otherwise use the default plan if (empty($plan)) { // TODO: Get default plan title from config $plan = Plan::withEnvTenantContext()->where('title', 'individual')->first(); } $request->plan = $plan; } return $request->plan; } /** * Login (kolab identity) validation * * @param string $login Login (local part of an email address) * @param string $domain Domain name * @param bool $external Enables additional checks for domain part * * @return array Error messages on validation error */ protected static function validateLogin($login, $domain, $external = false): ?array { // Validate login part alone $v = Validator::make( ['login' => $login], ['login' => ['required', 'string', new UserEmailLocal($external)]] ); if ($v->fails()) { return ['login' => $v->errors()->toArray()['login'][0]]; } $domains = $external ? null : Domain::getPublicDomains(); // Validate the domain $v = Validator::make( ['domain' => $domain], ['domain' => ['required', 'string', new UserEmailDomain($domains)]] ); if ($v->fails()) { return ['domain' => $v->errors()->toArray()['domain'][0]]; } $domain = Str::lower($domain); // Check if domain is already registered with us if ($external) { if (Domain::withTrashed()->where('namespace', $domain)->exists()) { - return ['domain' => \trans('validation.domainexists')]; + return ['domain' => self::trans('validation.domainexists')]; } } // Check if user with specified login already exists $email = $login . '@' . $domain; if (User::emailExists($email) || User::aliasExists($email) || \App\Group::emailExists($email)) { - return ['login' => \trans('validation.loginexists')]; + return ['login' => self::trans('validation.loginexists')]; } return null; } } diff --git a/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php b/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php index 7db10ccc..e6682c76 100644 --- a/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php +++ b/src/app/Http/Controllers/API/V4/Reseller/InvitationsController.php @@ -1,261 +1,261 @@ errorResponse(404); } /** * Remove the specified invitation. * * @param int $id Invitation identifier * * @return \Illuminate\Http\JsonResponse */ public function destroy($id) { $invitation = SignupInvitation::withSubjectTenantContext()->find($id); if (empty($invitation)) { return $this->errorResponse(404); } $invitation->delete(); return response()->json([ 'status' => 'success', - 'message' => trans('app.signup-invitation-delete-success'), + 'message' => self::trans('app.signup-invitation-delete-success'), ]); } /** * Show the form for editing the specified resource. * * @param int $id Invitation identifier * * @return \Illuminate\Http\JsonResponse */ public function edit($id) { return $this->errorResponse(404); } /** * Display a listing of the resource. * * @return \Illuminate\Http\JsonResponse */ public function index() { $pageSize = 10; $search = request()->input('search'); $page = intval(request()->input('page')) ?: 1; $hasMore = false; $result = SignupInvitation::withSubjectTenantContext() ->latest() ->limit($pageSize + 1) ->offset($pageSize * ($page - 1)); if ($search) { if (strpos($search, '@')) { $result->where('email', $search); } else { $result->whereLike('email', $search); } } $result = $result->get(); if (count($result) > $pageSize) { $result->pop(); $hasMore = true; } $result = $result->map(function ($invitation) { return $this->invitationToArray($invitation); }); return response()->json([ 'status' => 'success', 'list' => $result, 'count' => count($result), 'hasMore' => $hasMore, 'page' => $page, ]); } /** * Resend the specified invitation. * * @param int $id Invitation identifier * * @return \Illuminate\Http\JsonResponse */ public function resend($id) { $invitation = SignupInvitation::withSubjectTenantContext()->find($id); if (empty($invitation)) { return $this->errorResponse(404); } if ($invitation->isFailed() || $invitation->isSent()) { // Note: The email sending job will be dispatched by the observer $invitation->status = SignupInvitation::STATUS_NEW; $invitation->save(); } return response()->json([ 'status' => 'success', - 'message' => trans('app.signup-invitation-resend-success'), + 'message' => self::trans('app.signup-invitation-resend-success'), 'invitation' => $this->invitationToArray($invitation), ]); } /** * Store a newly created resource in storage. * * @param \Illuminate\Http\Request $request * * @return \Illuminate\Http\JsonResponse */ public function store(Request $request) { $errors = []; $invitations = []; $envTenantId = \config('app.tenant_id'); $subjectTenantId = auth()->user()->tenant_id; if (!empty($request->file) && is_object($request->file)) { // Expected a text/csv file with multiple email addresses if (!$request->file->isValid()) { $errors = ['file' => [$request->file->getErrorMessage()]]; } else { $fh = fopen($request->file->getPathname(), 'r'); $line_number = 0; $error = null; while ($line = fgetcsv($fh)) { $line_number++; // @phpstan-ignore-next-line if (count($line) >= 1 && $line[0]) { $email = trim($line[0]); if (strpos($email, '@')) { $v = Validator::make(['email' => $email], ['email' => 'email:filter|required']); if ($v->fails()) { $args = ['email' => $email, 'line' => $line_number]; - $error = trans('app.signup-invitations-csv-invalid-email', $args); + $error = self::trans('app.signup-invitations-csv-invalid-email', $args); break; } $invitations[] = ['email' => $email]; } } } fclose($fh); if ($error) { $errors = ['file' => $error]; } elseif (empty($invitations)) { - $errors = ['file' => trans('app.signup-invitations-csv-empty')]; + $errors = ['file' => self::trans('app.signup-invitations-csv-empty')]; } } } else { // Expected 'email' field with an email address $v = Validator::make($request->all(), ['email' => 'email|required']); if ($v->fails()) { $errors = $v->errors()->toArray(); } else { $invitations[] = ['email' => $request->email]; } } if (!empty($errors)) { return response()->json(['status' => 'error', 'errors' => $errors], 422); } $count = 0; foreach ($invitations as $idx => $invitation) { $inv = SignupInvitation::create($invitation); $count++; // Set the invitation tenant to the reseller tenant if ($envTenantId != $subjectTenantId) { $inv->tenant_id = $subjectTenantId; $inv->save(); } } return response()->json([ 'status' => 'success', - 'message' => self::trans_choice('app.signup-invitations-created', $count, ['count' => $count]), + 'message' => \trans_choice('app.signup-invitations-created', $count, ['count' => $count]), 'count' => $count, ]); } /** * Display the specified resource. * * @param int $id Invitation identifier * * @return \Illuminate\Http\JsonResponse */ public function show($id) { return $this->errorResponse(404); } /** * Update the specified resource in storage. * * @param \Illuminate\Http\Request $request * @param int $id * * @return \Illuminate\Http\JsonResponse */ public function update(Request $request, $id) { return $this->errorResponse(404); } /** * Convert an invitation object to an array for output * * @param \App\SignupInvitation $invitation The signup invitation object * * @return array */ protected static function invitationToArray(SignupInvitation $invitation): array { return [ 'id' => $invitation->id, 'email' => $invitation->email, 'isNew' => $invitation->isNew(), 'isSent' => $invitation->isSent(), 'isFailed' => $invitation->isFailed(), 'isCompleted' => $invitation->isCompleted(), 'created' => $invitation->created_at->toDateTimeString(), ]; } }