Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F174631
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
42 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 18, 6:35 PM (6 m, 32 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
119988
Default Alt Text
(42 KB)
Attached To
Mode
R2 kolab
Attached
Detach File
Event Timeline
Log In to Comment