Page MenuHomePhorge

No OneTemporary

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 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Laravel\Passport\TokenRepository;
use Laravel\Passport\RefreshTokenRepository;
class AuthController extends Controller
{
/**
* Get the authenticated User
*
* @return \Illuminate\Http\JsonResponse
*/
public function info()
{
$user = Auth::guard()->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 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Jobs\PasswordResetEmail;
use App\Rules\Password;
use App\User;
use App\VerificationCode;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
/**
* Password reset API
*/
class PasswordResetController extends Controller
{
/**
* Sends password reset code to the user's external email
*
* Verifies user email, sends verification email message.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function init(Request $request)
{
// Check required fields
$v = Validator::make($request->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 <short-code>-<code> 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 @@
<?php
namespace App\Http\Controllers\API;
use App\Http\Controllers\Controller;
use App\Jobs\SignupVerificationEmail;
use App\Discount;
use App\Domain;
use App\Plan;
use App\Providers\PaymentProvider;
use App\Rules\SignupExternalEmail;
use App\Rules\SignupToken;
use App\Rules\Password;
use App\Rules\UserEmailDomain;
use App\Rules\UserEmailLocal;
use App\SignupCode;
use App\SignupInvitation;
use App\User;
use App\Utils;
use App\VatRate;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
/**
* Signup process API
*/
class SignupController extends Controller
{
/**
* Returns plans definitions for signup.
*
* @param \Illuminate\Http\Request $request HTTP request
*
* @return \Illuminate\Http\JsonResponse JSON response
*/
public function plans(Request $request)
{
// Use reverse order just to have individual on left, group on right ;)
// But prefer monthly on left, yearly on right
$plans = Plan::withEnvTenantContext()->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 = '<tr class="subscription">'
. '<td>' . self::trans("app.signup-subscription-{$period}") . '</td>'
. '<td class="money">' . Utils::money($cost, $currency) . '</td>'
. '</tr>';
if ($discount) {
$summary .= '<tr class="discount">'
. '<td>' . self::trans('app.discount-code', ['code' => $discount->code]) . '</td>'
. '<td class="money">' . Utils::money(-$disc, $currency) . '</td>'
. '</tr>';
}
$summary .= '<tr class="sep"><td colspan="2"></td></tr>'
. '<tr class="total">'
. '<td>' . self::trans('app.total') . '</td>'
. '<td class="money">' . Utils::money($planCost, $currency) . '</td>'
. '</tr>';
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 .= '<tr class="vat-summary"><td colspan="2">*' . $content . '</td></tr>';
}
$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'] = '<table>' . $summary . '</table>';
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 @@
<?php
namespace App\Http\Controllers\API\V4\Reseller;
use App\Http\Controllers\Controller;
use App\SignupInvitation;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class InvitationsController extends Controller
{
/**
* Show the form for creating a new resource.
*
* @return \Illuminate\Http\JsonResponse
*/
public function create()
{
return $this->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(),
];
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 18, 6:35 PM (7 h, 38 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
119988
Default Alt Text
(42 KB)

Event Timeline