Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117891303
D2371.1775364499.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
37 KB
Referenced Files
None
Subscribers
None
D2371.1775364499.diff
View Options
diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php
--- a/src/app/Backends/LDAP.php
+++ b/src/app/Backends/LDAP.php
@@ -760,6 +760,7 @@
{
$firstName = $user->getSetting('first_name');
$lastName = $user->getSetting('last_name');
+ $isDegraded = $user->wallet()->owner->isDegraded();
$cn = "unknown";
$displayname = "";
@@ -820,13 +821,19 @@
$entry['nsroledn'][] = "cn=2fa-user,{$hostedRootDN}";
}
- if (in_array("activesync", $roles)) {
+ if (!$isDegraded && in_array("activesync", $roles)) {
$entry['nsroledn'][] = "cn=activesync-user,{$hostedRootDN}";
}
- if (!in_array("groupware", $roles)) {
+ if (!$isDegraded && !in_array("groupware", $roles)) {
$entry['nsroledn'][] = "cn=imap-user,{$hostedRootDN}";
}
+
+ if ($isDegraded) {
+ // FIXME: Should we use a role instead?
+ $entry['inetuserstatus'] = $user->status | User::STATUS_DEGRADED;
+ $entry['mailquota'] = 2 * 1048576;
+ }
}
/**
diff --git a/src/app/Console/Commands/UserDegrade.php b/src/app/Console/Commands/UserDegrade.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/UserDegrade.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class UserDegrade extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'user:degrade {user}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Degrade a user';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $user = \App\User::where('email', $this->argument('user'))->first();
+
+ if (!$user) {
+ return 1;
+ }
+
+ $user->degrade();
+ }
+}
diff --git a/src/app/Console/Commands/UserUndegrade.php b/src/app/Console/Commands/UserUndegrade.php
new file mode 100644
--- /dev/null
+++ b/src/app/Console/Commands/UserUndegrade.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+
+class UserUndegrade extends Command
+{
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'user:undegrade {user}';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Un-degrade a user';
+
+ /**
+ * Create a new command instance.
+ *
+ * @return void
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ }
+
+ /**
+ * Execute the console command.
+ *
+ * @return mixed
+ */
+ public function handle()
+ {
+ $user = \App\User::where('email', $this->argument('user'))->first();
+
+ if (!$user) {
+ return 1;
+ }
+
+ $user->undegrade();
+ }
+}
diff --git a/src/app/Console/Commands/WalletCharge.php b/src/app/Console/Commands/WalletCharge.php
--- a/src/app/Console/Commands/WalletCharge.php
+++ b/src/app/Console/Commands/WalletCharge.php
@@ -68,7 +68,9 @@
}
if ($wallet->balance < 0) {
- // Check the account balance, send notifications, suspend, delete
+ // Check the account balance, send notifications, (suspend, delete,) degrade
+ // TODO: For performance reasons we should probably skip the job
+ // if the wallet owner is degraded already
\App\Jobs\WalletCheck::dispatch($wallet);
}
}
diff --git a/src/app/Http/Controllers/API/V4/OpenViduController.php b/src/app/Http/Controllers/API/V4/OpenViduController.php
--- a/src/app/Http/Controllers/API/V4/OpenViduController.php
+++ b/src/app/Http/Controllers/API/V4/OpenViduController.php
@@ -213,7 +213,7 @@
$room = Room::where('name', $id)->first();
// Room does not exist, or the owner is deleted
- if (!$room || !$room->owner) {
+ if (!$room || !$room->owner || $room->owner->isDegraded(true)) {
return $this->errorResponse(404, \trans('meet.room-not-found'));
}
@@ -349,7 +349,7 @@
$room = Room::where('name', $id)->first();
// Room does not exist, or the owner is deleted
- if (!$room || !$room->owner) {
+ if (!$room || !$room->owner || $room->owner->isDegraded(true)) {
return $this->errorResponse(404);
}
diff --git a/src/app/Http/Controllers/API/V4/UsersController.php b/src/app/Http/Controllers/API/V4/UsersController.php
--- a/src/app/Http/Controllers/API/V4/UsersController.php
+++ b/src/app/Http/Controllers/API/V4/UsersController.php
@@ -525,6 +525,8 @@
'isSuspended' => $user->isSuspended(),
'isActive' => $user->isActive(),
'isDeleted' => $user->isDeleted() || $user->trashed(),
+ 'isDegraded' => $user->isDegraded(),
+ 'isAccountDegraded' => $user->isDegraded(true),
];
}
diff --git a/src/app/Jobs/WalletCheck.php b/src/app/Jobs/WalletCheck.php
--- a/src/app/Jobs/WalletCheck.php
+++ b/src/app/Jobs/WalletCheck.php
@@ -19,6 +19,8 @@
use Queueable;
use SerializesModels;
+ public const THRESHOLD_DEGRADE = 'degrade';
+ public const THRESHOLD_BEFORE_DEGRADE = 'before_degrade';
public const THRESHOLD_DELETE = 'delete';
public const THRESHOLD_BEFORE_DELETE = 'before_delete';
public const THRESHOLD_SUSPEND = 'suspend';
@@ -64,62 +66,80 @@
}
$now = Carbon::now();
-
- // Delete the account
- if (self::threshold($this->wallet, self::THRESHOLD_DELETE) < $now) {
- $this->deleteAccount();
- return self::THRESHOLD_DELETE;
- }
-
- // Warn about the upcomming account deletion
- if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_DELETE) < $now) {
- $this->warnBeforeDelete();
- return self::THRESHOLD_BEFORE_DELETE;
+/*
+ // Steps for old "first suspend then delete" approach
+ $steps = [
+ // Send the initial reminder
+ self::THRESHOLD_INITIAL => 'initialReminder',
+ // Try to top-up the wallet before the second reminder
+ self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet',
+ // Send the second reminder
+ self::THRESHOLD_REMINDER => 'secondReminder',
+ // Try to top-up the wallet before suspending the account
+ self::THRESHOLD_BEFORE_SUSPEND => 'topUpWallet',
+ // Suspend the account
+ self::THRESHOLD_SUSPEND => 'suspendAccount',
+ // Warn about the upcomming account deletion
+ self::THRESHOLD_BEFORE_DELETE => 'warnBeforeDelete',
+ // Delete the account
+ self::THRESHOLD_DELETE => 'deleteAccount',
+ ];
+*/
+ // Steps for "demote instead of suspend+delete" approach
+ $steps = [
+ // Send the initial reminder
+ self::THRESHOLD_INITIAL => 'initialReminderForDegrade',
+ // Try to top-up the wallet before the second reminder
+ self::THRESHOLD_BEFORE_REMINDER => 'topUpWallet',
+ // Send the second reminder
+ self::THRESHOLD_REMINDER => 'secondReminderForDegrade',
+ // Try to top-up the wallet before the account degradation
+ self::THRESHOLD_BEFORE_DEGRADE => 'topUpWallet',
+ // Degrade the account
+ self::THRESHOLD_DEGRADE => 'degradeAccount',
+ ];
+
+ foreach (array_reverse($steps, true) as $type => $method) {
+ if (self::threshold($this->wallet, $type) < $now) {
+ $this->{$method}();
+ return $type;
+ }
}
- // Suspend the account
- if (self::threshold($this->wallet, self::THRESHOLD_SUSPEND) < $now) {
- $this->suspendAccount();
- return self::THRESHOLD_SUSPEND;
- }
+ return null;
+ }
- // Try to top-up the wallet before suspending the account
- if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_SUSPEND) < $now) {
- PaymentsController::topUpWallet($this->wallet);
- return self::THRESHOLD_BEFORE_SUSPEND;
+ /**
+ * Send the initial reminder (for the suspend+delete process)
+ */
+ protected function initialReminder()
+ {
+ if ($this->wallet->getSetting('balance_warning_initial')) {
+ return;
}
- // Send the second reminder
- if (self::threshold($this->wallet, self::THRESHOLD_REMINDER) < $now) {
- $this->secondReminder();
- return self::THRESHOLD_REMINDER;
- }
+ // TODO: Should we check if the account is already suspended?
- // Try to top-up the wallet before the second reminder
- if (self::threshold($this->wallet, self::THRESHOLD_BEFORE_REMINDER) < $now) {
- PaymentsController::topUpWallet($this->wallet);
- return self::THRESHOLD_BEFORE_REMINDER;
- }
+ $label = "Notification sent for";
- // Send the initial reminder
- if (self::threshold($this->wallet, self::THRESHOLD_INITIAL) < $now) {
- $this->initialReminder();
- return self::THRESHOLD_INITIAL;
- }
+ $this->sendMail(\App\Mail\NegativeBalance::class, false, $label);
- return null;
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_initial', $now);
}
/**
- * Send the initial reminder
+ * Send the initial reminder (for the process of degrading a account)
*/
- protected function initialReminder()
+ protected function initialReminderForDegrade()
{
if ($this->wallet->getSetting('balance_warning_initial')) {
return;
}
- // TODO: Should we check if the account is already suspended?
+ if ($this->wallet->owner && $this->wallet->owner->isDegraded()) {
+ return;
+ }
$label = "Notification sent for";
@@ -130,7 +150,7 @@
}
/**
- * Send the second reminder
+ * Send the second reminder (for the suspend+delete process)
*/
protected function secondReminder()
{
@@ -148,6 +168,27 @@
$this->wallet->setSetting('balance_warning_reminder', $now);
}
+ /**
+ * Send the second reminder (for the process of degrading a account)
+ */
+ protected function secondReminderForDegrade()
+ {
+ if ($this->wallet->getSetting('balance_warning_reminder')) {
+ return;
+ }
+
+ if ($this->wallet->owner && $this->wallet->owner->isDegraded()) {
+ return;
+ }
+
+ $label = "Reminder sent for";
+
+ $this->sendMail(\App\Mail\NegativeBalanceReminderDegrade::class, true, $label);
+
+ $now = \Carbon\Carbon::now()->toDateTimeString();
+ $this->wallet->setSetting('balance_warning_reminder', $now);
+ }
+
/**
* Suspend the account (and send the warning)
*/
@@ -203,6 +244,36 @@
$this->wallet->setSetting('balance_warning_before_delete', $now);
}
+ /**
+ * Degrade the account
+ */
+ protected function degradeAccount()
+ {
+ // TODO: This will not work when we actually allow multiple-wallets per account
+ // but in this case we anyway have to change the whole thing
+ // and calculate summarized balance from all wallets.
+ if (!$this->wallet->owner || $this->wallet->owner->isDegraded()) {
+ return;
+ }
+
+ $email = $this->wallet->owner->email;
+
+ // The dirty work will be done by UserObserver
+ $this->wallet->owner->degrade();
+
+ \Log::info(
+ sprintf(
+ "[WalletCheck] Account degraded %s (%s)",
+ $this->wallet->id,
+ $email
+ )
+ );
+
+ $label = "Degradation notification sent for";
+
+ $this->sendMail(\App\Mail\NegativeBalanceDegraded::class, true, $label);
+ }
+
/**
* Delete the account
*/
@@ -292,47 +363,50 @@
$negative_since = new Carbon($negative_since);
}
- $remind = 7; // remind after first X days
- $suspend = 14; // suspend after next X days
- $delete = 21; // delete after next X days
- $warn = 3; // warn about delete on X days before delete
-
- // Acount deletion
- if ($type == self::THRESHOLD_DELETE) {
- return $negative_since->addDays($delete + $suspend + $remind);
- }
-
- // Warning about the upcomming account deletion
- if ($type == self::THRESHOLD_BEFORE_DELETE) {
- return $negative_since->addDays($delete + $suspend + $remind - $warn);
- }
-
- // Account suspension
- if ($type == self::THRESHOLD_SUSPEND) {
- return $negative_since->addDays($suspend + $remind);
- }
-
- // A day before account suspension
- if ($type == self::THRESHOLD_BEFORE_SUSPEND) {
- return $negative_since->addDays($suspend + $remind - 1);
- }
-
- // Second notification
- if ($type == self::THRESHOLD_REMINDER) {
- return $negative_since->addDays($remind);
- }
-
- // A day before the second reminder
- if ($type == self::THRESHOLD_BEFORE_REMINDER) {
- return $negative_since->addDays($remind - 1);
- }
-
// Initial notification
// Give it an hour so the async recurring payment has a chance to be finished
if ($type == self::THRESHOLD_INITIAL) {
return $negative_since->addHours(1);
}
+/*
+ $thresholds = [
+ // A day before the second reminder
+ self::THRESHOLD_BEFORE_REMINDER => 7 - 1,
+ // Second notification
+ self::THRESHOLD_REMINDER => 7,
+ // A day before account suspension
+ self::THRESHOLD_BEFORE_SUSPEND => 14 + 7 - 1,
+ // Account suspension
+ self::THRESHOLD_SUSPEND => 14 + 7,
+ // Warning about the upcomming account deletion
+ self::THRESHOLD_BEFORE_DELETE => 21 + 14 + 7 - 3,
+ // Acount deletion
+ self::THRESHOLD_DELETE => 21 + 14 + 7,
+ ];
+*/
+ $thresholds = [
+ // Top-up a walet before the second notification
+ self::THRESHOLD_BEFORE_REMINDER => 7 - 1,
+ // Second notification
+ self::THRESHOLD_REMINDER => 7,
+ // Last chance to top-up the wallet
+ self::THRESHOLD_BEFORE_DEGRADE => 13,
+ // Account degradation
+ self::THRESHOLD_DEGRADE => 14,
+ ];
+
+ if (!empty($thresholds[$type])) {
+ return $negative_since->addDays($thresholds[$type]);
+ }
return null;
}
+
+ /**
+ * Try to automatically top-up the wallet
+ */
+ protected function topUpWallet(): void
+ {
+ PaymentsController::topUpWallet($this->wallet);
+ }
}
diff --git a/src/app/Mail/NegativeBalanceDegraded.php b/src/app/Mail/NegativeBalanceDegraded.php
new file mode 100644
--- /dev/null
+++ b/src/app/Mail/NegativeBalanceDegraded.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace App\Mail;
+
+use App\Jobs\WalletCheck;
+use App\User;
+use App\Utils;
+use App\Wallet;
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class NegativeBalanceDegraded extends Mailable
+{
+ use Queueable;
+ use SerializesModels;
+
+ /** @var \App\Wallet A wallet with a negative balance */
+ protected $wallet;
+
+ /** @var \App\User A wallet controller to whom the email is being sent */
+ protected $user;
+
+
+ /**
+ * Create a new message instance.
+ *
+ * @param \App\Wallet $wallet A wallet
+ * @param \App\User $user An email recipient
+ *
+ * @return void
+ */
+ public function __construct(Wallet $wallet, User $user)
+ {
+ $this->wallet = $wallet;
+ $this->user = $user;
+ }
+
+ /**
+ * Build the message.
+ *
+ * @return $this
+ */
+ public function build()
+ {
+ $subject = \trans('mail.negativebalancedegraded-subject', ['site' => \config('app.name')]);
+
+ $this->view('emails.html.negative_balance_degraded')
+ ->text('emails.plain.negative_balance_degraded')
+ ->subject($subject)
+ ->with([
+ 'site' => \config('app.name'),
+ 'subject' => $subject,
+ 'username' => $this->user->name(true),
+ 'supportUrl' => \config('app.support_url'),
+ 'walletUrl' => Utils::serviceUrl('/wallet'),
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Render the mail template with fake data
+ *
+ * @param string $type Output format ('html' or 'text')
+ *
+ * @return string HTML or Plain Text output
+ */
+ public static function fakeRender(string $type = 'html'): string
+ {
+ $wallet = new Wallet();
+ $user = new User();
+
+ $mail = new self($wallet, $user);
+
+ return Helper::render($mail, $type);
+ }
+}
diff --git a/src/app/Mail/NegativeBalanceReminderDegrade.php b/src/app/Mail/NegativeBalanceReminderDegrade.php
new file mode 100644
--- /dev/null
+++ b/src/app/Mail/NegativeBalanceReminderDegrade.php
@@ -0,0 +1,81 @@
+<?php
+
+namespace App\Mail;
+
+use App\Jobs\WalletCheck;
+use App\User;
+use App\Utils;
+use App\Wallet;
+use Illuminate\Bus\Queueable;
+use Illuminate\Mail\Mailable;
+use Illuminate\Queue\SerializesModels;
+
+class NegativeBalanceReminderDegrade extends Mailable
+{
+ use Queueable;
+ use SerializesModels;
+
+ /** @var \App\Wallet A wallet with a negative balance */
+ protected $wallet;
+
+ /** @var \App\User A wallet controller to whom the email is being sent */
+ protected $user;
+
+
+ /**
+ * Create a new message instance.
+ *
+ * @param \App\Wallet $wallet A wallet
+ * @param \App\User $user An email recipient
+ *
+ * @return void
+ */
+ public function __construct(Wallet $wallet, User $user)
+ {
+ $this->wallet = $wallet;
+ $this->user = $user;
+ }
+
+ /**
+ * Build the message.
+ *
+ * @return $this
+ */
+ public function build()
+ {
+ $threshold = WalletCheck::threshold($this->wallet, WalletCheck::THRESHOLD_DEGRADE);
+
+ $subject = \trans('mail.negativebalancereminder-subject', ['site' => \config('app.name')]);
+
+ $this->view('emails.html.negative_balance_reminder')
+ ->text('emails.plain.negative_balance_reminder')
+ ->subject($subject)
+ ->with([
+ 'site' => \config('app.name'),
+ 'subject' => $subject,
+ 'username' => $this->user->name(true),
+ 'supportUrl' => \config('app.support_url'),
+ 'walletUrl' => Utils::serviceUrl('/wallet'),
+ 'date' => $threshold->toDateString(),
+ ]);
+
+ return $this;
+ }
+
+ /**
+ * Render the mail template with fake data
+ *
+ * @param string $type Output format ('html' or 'text')
+ *
+ * @return string HTML or Plain Text output
+ */
+ public static function fakeRender(string $type = 'html'): string
+ {
+ $wallet = new Wallet();
+ $user = new User();
+
+ $mail = new self($wallet, $user);
+
+ return Helper::render($mail, $type);
+ }
+}
diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php
--- a/src/app/Observers/EntitlementObserver.php
+++ b/src/app/Observers/EntitlementObserver.php
@@ -116,6 +116,10 @@
$owner = $entitlement->wallet->owner;
+ if ($owner->isDegraded()) {
+ return;
+ }
+
// Determine if we're still within the free first month
$freeMonthEnds = $owner->created_at->copy()->addMonthsWithoutOverflow(1);
diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php
--- a/src/app/Observers/UserObserver.php
+++ b/src/app/Observers/UserObserver.php
@@ -349,14 +349,43 @@
}
/**
- * Handle the "updating" event.
+ * Handle the "updated" event.
*
* @param User $user The user that is being updated.
*
* @return void
*/
- public function updating(User $user)
+ public function updated(User $user)
{
\App\Jobs\User\UpdateJob::dispatch($user->id);
+
+ $oldStatus = $user->getOriginal('status');
+ $newStatus = $user->status;
+
+ if (($oldStatus & User::STATUS_DEGRADED) !== ($newStatus & User::STATUS_DEGRADED)) {
+ $wallets = [];
+
+ // Charge all entitlements as if they were being deleted,
+ // but don't delete them. Just debit the wallet and update
+ // entitlements' updated_at timestamp. On un-degrade we still
+ // update updated_at, but with no debit (the cost is 0 on a degraded account).
+ foreach ($user->wallets as $wallet) {
+ $wallet->updateEntitlements($user->isDegraded());
+ $wallets[] = $wallet->id;
+ }
+
+ // (Un-)degrade users by invoking an update job.
+ // LDAP backend will read the wallet owner's degraded status and
+ // set LDAP attributes accordingly.
+ // We do not change their status as their wallets have its own state
+ \App\Entitlement::whereIn('wallet_id', $wallets)
+ ->where('entitleable_id', '!=', $user->id)
+ ->where('entitleable_type', User::class)
+ ->pluck('entitleable_id')
+ ->unique()
+ ->each(function ($user_id) {
+ \App\Jobs\User\UpdateJob::dispatch($user_id);
+ });
+ }
}
}
diff --git a/src/app/Observers/WalletObserver.php b/src/app/Observers/WalletObserver.php
--- a/src/app/Observers/WalletObserver.php
+++ b/src/app/Observers/WalletObserver.php
@@ -95,10 +95,12 @@
'balance_warning_before_delete' => null,
]);
- // Unsuspend the account/domains/users
+ // Un-suspend and un-degrade the account/domains/users
if ($wallet->owner) {
$wallet->owner->unsuspend();
+ $wallet->owner->undegrade();
}
+
foreach ($wallet->entitlements as $entitlement) {
if (
$entitlement->entitleable_type == \App\Domain::class
diff --git a/src/app/User.php b/src/app/User.php
--- a/src/app/User.php
+++ b/src/app/User.php
@@ -42,6 +42,8 @@
public const STATUS_LDAP_READY = 1 << 4;
// user mailbox has been created in IMAP
public const STATUS_IMAP_READY = 1 << 5;
+ // user in "limited feature-set" state
+ public const STATUS_DEGRADED = 1 << 6;
// change the default primary key type
@@ -262,6 +264,21 @@
return $this->canDelete($object);
}
+ /**
+ * Degrade the user
+ *
+ * @return void
+ */
+ public function degrade(): void
+ {
+ if ($this->isDegraded()) {
+ return;
+ }
+
+ $this->status |= User::STATUS_DEGRADED;
+ $this->save();
+ }
+
/**
* Return the \App\Domain for this user.
*
@@ -449,7 +466,7 @@
}
/**
- * Returns whether this domain is active.
+ * Returns whether this user is active.
*
* @return bool
*/
@@ -459,7 +476,28 @@
}
/**
- * Returns whether this domain is deleted.
+ * Returns whether this user (or its wallet owner) is degraded.
+ *
+ * @param bool $owner Check also the wallet owner instead just the user himself
+ *
+ * @return bool
+ */
+ public function isDegraded(bool $owner = false): bool
+ {
+ if ($this->status & self::STATUS_DEGRADED) {
+ return true;
+ }
+
+ if ($owner) {
+ $owner = $this->wallet()->owner;
+ return $owner && $owner->isDegraded();
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns whether this user is deleted.
*
* @return bool
*/
@@ -469,8 +507,7 @@
}
/**
- * Returns whether this (external) domain has been verified
- * to exist in DNS.
+ * Returns whether this user is registered in IMAP.
*
* @return bool
*/
@@ -500,7 +537,7 @@
}
/**
- * Returns whether this domain is suspended.
+ * Returns whether this user is suspended.
*
* @return bool
*/
@@ -574,7 +611,7 @@
}
/**
- * Suspend this domain.
+ * Suspend this user.
*
* @return void
*/
@@ -589,7 +626,22 @@
}
/**
- * Unsuspend this domain.
+ * Un-degrade this user.
+ *
+ * @return void
+ */
+ public function undegrade(): void
+ {
+ if (!$this->isDegraded()) {
+ return;
+ }
+
+ $this->status ^= User::STATUS_DEGRADED;
+ $this->save();
+ }
+
+ /**
+ * Unsuspend this user.
*
* @return void
*/
@@ -705,6 +757,7 @@
self::STATUS_DELETED,
self::STATUS_LDAP_READY,
self::STATUS_IMAP_READY,
+ self::STATUS_DEGRADED,
];
foreach ($allowed_values as $value) {
diff --git a/src/app/Wallet.php b/src/app/Wallet.php
--- a/src/app/Wallet.php
+++ b/src/app/Wallet.php
@@ -58,7 +58,14 @@
}
}
- public function chargeEntitlements($apply = true)
+ /**
+ * Charge entitlements in the wallet
+ *
+ * @param bool $apply Set to false for a dry-run mode
+ *
+ * @return int Charged amount in cents
+ */
+ public function chargeEntitlements($apply = true): int
{
// This wallet has been created less than a month ago, this is the trial period
if ($this->owner->created_at >= Carbon::now()->subMonthsWithoutOverflow(1)) {
@@ -78,8 +85,11 @@
$charges = 0;
$discount = $this->getDiscountRate();
+ $isDegraded = $this->owner->isDegraded();
- DB::beginTransaction();
+ if ($apply) {
+ DB::beginTransaction();
+ }
// used to parent individual entitlement billings to the wallet debit.
$entitlementTransactions = [];
@@ -102,6 +112,10 @@
$cost = (int) ($entitlement->cost * $discount * $diff);
+ if ($isDegraded) {
+ $cost = 0;
+ }
+
$charges += $cost;
// if we're in dry-run, you know...
@@ -127,10 +141,9 @@
if ($apply) {
$this->debit($charges, $entitlementTransactions);
+ DB::commit();
}
- DB::commit();
-
return $charges;
}
@@ -397,4 +410,72 @@
]
);
}
+
+ /**
+ * Force-update entitlements' updated_at, charge if needed.
+ *
+ * @param bool $withCost When enabled the cost will be charged
+ *
+ * @return int Charged amount in cents
+ */
+ public function updateEntitlements($withCost = true): int
+ {
+ $charges = 0;
+ $discount = $this->getDiscountRate();
+ $now = Carbon::now();
+
+ DB::beginTransaction();
+
+ // used to parent individual entitlement billings to the wallet debit.
+ $entitlementTransactions = [];
+
+ foreach ($this->entitlements()->get()->fresh() as $entitlement) {
+ $cost = 0;
+ $diffInDays = $entitlement->updated_at->diffInDays($now);
+
+ // This entitlement has been created less than or equal to 14 days ago (this is at
+ // maximum the fourteenth 24-hour period).
+ if ($entitlement->created_at > Carbon::now()->subDays(14)) {
+ // $cost=0
+ } elseif ($withCost && $diffInDays > 0) {
+ // The price per day is based on the number of days in the last month
+ // or the current month if the period does not overlap with the previous month
+ // FIXME: This really should be simplified to constant $daysInMonth=30
+ if ($now->day >= $diffInDays && $now->month == $entitlement->updated_at->month) {
+ $daysInMonth = $now->daysInMonth;
+ } else {
+ $daysInMonth = \App\Utils::daysInLastMonth();
+ }
+
+ $pricePerDay = $entitlement->cost / $daysInMonth;
+
+ $cost = (int) (round($pricePerDay * $discount * $diffInDays, 0));
+ }
+
+ if ($diffInDays > 0) {
+ $entitlement->updated_at->setDateFrom($now);
+ $entitlement->save();
+ }
+
+ if ($cost == 0) {
+ continue;
+ }
+
+ $charges += $cost;
+
+ // FIXME: Shouldn't we store also cost=0 transactions (to have the full history)?
+ $entitlementTransactions[] = $entitlement->createTransaction(
+ \App\Transaction::ENTITLEMENT_BILLED,
+ $cost
+ );
+ }
+
+ if ($charges > 0) {
+ $this->debit($charges, $entitlementTransactions);
+ }
+
+ DB::commit();
+
+ return $charges;
+ }
}
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -300,6 +300,9 @@
return 'Active'
},
+ isDegraded() {
+ return store.state.authInfo.isAccountDegraded
+ },
pageName(path) {
let page = this.$route.path
@@ -337,7 +340,7 @@
return 'text-muted'
}
- if (user.isSuspended) {
+ if (user.isDegraded || user.isSuspended) {
return 'text-warning'
}
@@ -352,6 +355,10 @@
return 'Deleted'
}
+ if (user.isDegraded) {
+ return 'Degraded'
+ }
+
if (user.isSuspended) {
return 'Suspended'
}
diff --git a/src/resources/lang/en/mail.php b/src/resources/lang/en/mail.php
--- a/src/resources/lang/en/mail.php
+++ b/src/resources/lang/en/mail.php
@@ -29,6 +29,14 @@
'negativebalancereminder-body-warning' => "Please, be aware that your account will be suspended "
. "if your account balance is not settled by :date.",
+ 'negativebalancereminderdegrade-body-warning' => "Please, be aware that your account will be degraded "
+ . "if your account balance is not settled by :date.",
+
+ 'negativebalancedegraded-subject' => ":site Account Degraded",
+ 'negativebalancedegraded-body' => "Your :site account has been degraded for having a negative balance for too long. "
+ . "Consider setting up an automatic payment to avoid messages like this in the future.",
+ 'negativebalancedegraded-body-ext' => "Settle up now to undegrade your account:",
+
'negativebalancesuspended-subject' => ":site Account Suspended",
'negativebalancesuspended-body' => "Your :site account has been suspended for having a negative balance for too long. "
. "Consider setting up an automatic payment to avoid messages like this in the future.",
diff --git a/src/resources/views/emails/html/negative_balance_degraded.blade.php b/src/resources/views/emails/html/negative_balance_degraded.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/html/negative_balance_degraded.blade.php
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <p>{{ __('mail.header', ['name' => $username]) }}</p>
+
+ <p>{{ __('mail.negativebalancedegraded-body', ['site' => $site]) }}</p>
+ <p>{{ __('mail.negativebalancedegraded-body-ext', ['site' => $site]) }}</p>
+ <p><a href="{{ $walletUrl }}">{{ $walletUrl }}</a></p>
+
+@if ($supportUrl)
+ <p>{{ __('mail.support', ['site' => $site]) }}</p>
+ <p><a href="{{ $supportUrl }}">{{ $supportUrl }}</a></p>
+@endif
+
+ <p>{{ __('mail.footer1') }}</p>
+ <p>{{ __('mail.footer2', ['site' => $site]) }}</p>
+ </body>
+</html>
diff --git a/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php b/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/html/negative_balance_reminder_degrade.blade.php
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <p>{{ __('mail.header', ['name' => $username]) }}</p>
+
+ <p>{{ __('mail.negativebalancereminder-body', ['site' => $site]) }}</p>
+ <p>{{ __('mail.negativebalancereminder-body-ext', ['site' => $site]) }}</p>
+ <p><a href="{{ $walletUrl }}">{{ $walletUrl }}</a></p>
+ <p><b>{{ __('mail.negativebalancereminderdegrade-body-warning', ['site' => $site, 'date' => $date]) }}</b></p>
+
+@if ($supportUrl)
+ <p>{{ __('mail.support', ['site' => $site]) }}</p>
+ <p><a href="{{ $supportUrl }}">{{ $supportUrl }}</a></p>
+@endif
+
+ <p>{{ __('mail.footer1') }}</p>
+ <p>{{ __('mail.footer2', ['site' => $site]) }}</p>
+ </body>
+</html>
diff --git a/src/resources/views/emails/plain/negative_balance_degraded.blade.php b/src/resources/views/emails/plain/negative_balance_degraded.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/plain/negative_balance_degraded.blade.php
@@ -0,0 +1,17 @@
+{!! __('mail.header', ['name' => $username]) !!}
+
+{!! __('mail.negativebalancedegraded-body', ['site' => $site]) !!}
+
+{!! __('mail.negativebalancedegraded-body-ext', ['site' => $site]) !!}
+
+{!! $walletUrl !!}
+
+@if ($supportUrl)
+{!! __('mail.support', ['site' => $site]) !!}
+
+{!! $supportUrl !!}
+@endif
+
+--
+{!! __('mail.footer1') !!}
+{!! __('mail.footer2', ['site' => $site]) !!}
diff --git a/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php b/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php
new file mode 100644
--- /dev/null
+++ b/src/resources/views/emails/plain/negative_balance_reminder_degrade.blade.php
@@ -0,0 +1,19 @@
+{!! __('mail.header', ['name' => $username]) !!}
+
+{!! __('mail.negativebalancereminder-body', ['site' => $site]) !!}
+
+{!! __('mail.negativebalancereminder-body-ext', ['site' => $site]) !!}
+
+{!! $walletUrl !!}
+
+{!! __('mail.negativebalancereminderdegrade-body-warning', ['site' => $site, 'date' => $date]) !!}
+
+@if ($supportUrl)
+{!! __('mail.support', ['site' => $site]) !!}
+
+{!! $supportUrl !!}
+@endif
+
+--
+{!! __('mail.footer1') !!}
+{!! __('mail.footer2', ['site' => $site]) !!}
diff --git a/src/resources/vue/Dashboard.vue b/src/resources/vue/Dashboard.vue
--- a/src/resources/vue/Dashboard.vue
+++ b/src/resources/vue/Dashboard.vue
@@ -2,6 +2,12 @@
<div class="container" dusk="dashboard-component">
<status-component :status="status" @status-update="statusUpdate"></status-component>
+ <div id="status-degraded" v-if="$root.isDegraded()" class="d-flex justify-content-center">
+ <p class="alert alert-danger">
+ The account is degraded. Some features has been disabled. Please, make a payment.
+ </p>
+ </div>
+
<div id="dashboard-nav">
<router-link class="card link-profile" :to="{ name: 'profile' }">
<svg-icon icon="user-cog"></svg-icon><span class="name">Your profile</span>
@@ -16,7 +22,7 @@
<svg-icon icon="wallet"></svg-icon><span class="name">Wallet</span>
<span v-if="balance < 0" class="badge badge-danger">{{ $root.price(balance) }}</span>
</router-link>
- <router-link v-if="$root.hasSKU('meet')" class="card link-chat" :to="{ name: 'rooms' }">
+ <router-link v-if="$root.hasSKU('meet') && !$root.isDegraded()" class="card link-chat" :to="{ name: 'rooms' }">
<svg-icon icon="comments"></svg-icon><span class="name">Video chat</span>
<span class="badge badge-primary">beta</span>
</router-link>
diff --git a/src/resources/vue/Rooms.vue b/src/resources/vue/Rooms.vue
--- a/src/resources/vue/Rooms.vue
+++ b/src/resources/vue/Rooms.vue
@@ -81,7 +81,7 @@
}
},
mounted() {
- if (!this.$root.hasSKU('meet')) {
+ if (!this.$root.hasSKU('meet') || this.$root.isDegraded()) {
this.$root.errorPage(403)
return
}
diff --git a/src/resources/vue/User/List.vue b/src/resources/vue/User/List.vue
--- a/src/resources/vue/User/List.vue
+++ b/src/resources/vue/User/List.vue
@@ -4,7 +4,7 @@
<div class="card-body">
<div class="card-title">
User Accounts
- <router-link class="btn btn-primary float-right create-user" :to="{ path: 'user/new' }" tag="button">
+ <router-link v-if="!$root.isDegraded()" class="btn btn-primary float-right create-user" :to="{ path: 'user/new' }" tag="button">
<svg-icon icon="user"></svg-icon> Create user
</router-link>
</div>
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sun, Apr 5, 4:48 AM (16 h, 58 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18832371
Default Alt Text
D2371.1775364499.diff (37 KB)
Attached To
Mode
D2371: Degraded accounts
Attached
Detach File
Event Timeline